Skip to content

Commit f68b035

Browse files
committed
fix app language switching refresh
1 parent f7c96f8 commit f68b035

11 files changed

Lines changed: 121 additions & 42 deletions

File tree

app/src/main/java/com/sevtinge/hyperceiler/home/IconTitleLoader.java

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,21 @@
99
import android.text.TextUtils;
1010

1111
import com.sevtinge.hyperceiler.utils.AppIconCache;
12+
import com.sevtinge.hyperceiler.utils.LanguageHelper;
1213
import com.sevtinge.hyperceiler.utils.ThreadUtils;
1314

1415
import java.util.List;
16+
import java.util.Locale;
1517
import java.util.Map;
1618
import java.util.concurrent.ConcurrentHashMap;
1719
import java.util.concurrent.CountDownLatch;
1820
import java.util.concurrent.TimeUnit;
21+
import java.util.concurrent.atomic.AtomicInteger;
1922

2023
public class IconTitleLoader {
2124

2225
private static final Map<String, CharSequence> sLabelCache = new ConcurrentHashMap<>();
26+
private static final AtomicInteger sLabelCacheGeneration = new AtomicInteger();
2327

2428
public record AppInfo(Drawable icon, CharSequence label) {
2529
}
@@ -29,7 +33,7 @@ public record AppInfo(Drawable icon, CharSequence label) {
2933
* 图标缓存按包名和尺寸区分,避免不同列表互相串尺寸。
3034
*/
3135
public static AppInfo getCached(Context context, String pkg, int iconSizePx) {
32-
CharSequence label = sLabelCache.get(buildLabelCacheKey(context, pkg));
36+
CharSequence label = sLabelCache.get(buildLabelCacheKey(pkg, getLocaleTag(context)));
3337
Drawable icon = AppIconCache.getCached(context, pkg, iconSizePx);
3438
if (label == null || icon == null) {
3539
return null;
@@ -40,6 +44,9 @@ public static AppInfo getCached(Context context, String pkg, int iconSizePx) {
4044
public static void load(Context context, String pkg, int iconSizePx, LoadCallback callback) {
4145
Context labelContext = context;
4246
Context appContext = context.getApplicationContext();
47+
Locale locale = getLocaleSnapshot(labelContext);
48+
String localeTag = locale.toLanguageTag();
49+
int generation = sLabelCacheGeneration.get();
4350
AppInfo cached = getCached(labelContext, pkg, iconSizePx);
4451
if (cached != null) {
4552
callback.onReady(cached);
@@ -50,14 +57,18 @@ public static void load(Context context, String pkg, int iconSizePx, LoadCallbac
5057
try {
5158
PackageManager pm = labelContext.getPackageManager();
5259
ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
53-
String labelCacheKey = buildLabelCacheKey(labelContext, pkg);
54-
CharSequence label = sLabelCache.computeIfAbsent(labelCacheKey, ignored -> loadLabel(labelContext, info, pm));
60+
String labelCacheKey = buildLabelCacheKey(pkg, localeTag);
61+
CharSequence label = sLabelCache.computeIfAbsent(labelCacheKey, ignored -> loadLabel(labelContext, info, pm, locale));
5562
Drawable icon = AppIconCache.loadIcon(appContext, pkg, iconSizePx);
5663
if (icon == null) {
5764
icon = info.loadIcon(pm);
5865
}
5966
AppInfo result = new AppInfo(icon, label);
60-
ThreadUtils.postOnMainThread(() -> callback.onReady(result));
67+
ThreadUtils.postOnMainThread(() -> {
68+
if (generation == sLabelCacheGeneration.get()) {
69+
callback.onReady(result);
70+
}
71+
});
6172
} catch (Exception ignored) {}
6273
});
6374
}
@@ -69,9 +80,12 @@ public static void load(Context context, String pkg, int iconSizePx, LoadCallbac
6980
public static void preloadAll(Context context, List<String> packageNames, int iconSizePx, Runnable onComplete) {
7081
Context labelContext = context;
7182
Context appContext = context.getApplicationContext();
83+
Locale locale = getLocaleSnapshot(labelContext);
84+
String localeTag = locale.toLanguageTag();
85+
int generation = sLabelCacheGeneration.get();
7286
// 过滤出 label 或当前尺寸图标尚未缓存的包名
7387
List<String> toLoad = packageNames.stream()
74-
.filter(pkg -> pkg != null && (sLabelCache.get(buildLabelCacheKey(labelContext, pkg)) == null
88+
.filter(pkg -> pkg != null && (sLabelCache.get(buildLabelCacheKey(pkg, localeTag)) == null
7589
|| AppIconCache.getCached(appContext, pkg, iconSizePx) == null))
7690
.toList();
7791

@@ -86,7 +100,10 @@ public static void preloadAll(Context context, List<String> packageNames, int ic
86100
try {
87101
PackageManager pm = labelContext.getPackageManager();
88102
ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
89-
sLabelCache.put(buildLabelCacheKey(labelContext, pkg), loadLabel(labelContext, info, pm));
103+
CharSequence label = loadLabel(labelContext, info, pm, locale);
104+
if (generation == sLabelCacheGeneration.get()) {
105+
sLabelCache.put(buildLabelCacheKey(pkg, localeTag), label);
106+
}
90107
AppIconCache.loadIcon(appContext, pkg, iconSizePx);
91108
} catch (Exception ignored) {
92109
} finally {
@@ -100,29 +117,35 @@ public static void preloadAll(Context context, List<String> packageNames, int ic
100117
try {
101118
latch.await(5, TimeUnit.SECONDS);
102119
} catch (InterruptedException ignored) {}
103-
ThreadUtils.postOnMainThread(onComplete);
120+
ThreadUtils.postOnMainThread(() -> {
121+
if (generation == sLabelCacheGeneration.get()) {
122+
onComplete.run();
123+
}
124+
});
104125
});
105126
}
106127

107128
public static void clearLabelCache() {
108129
sLabelCache.clear();
130+
sLabelCacheGeneration.incrementAndGet();
109131
}
110132

111-
private static String buildLabelCacheKey(Context context, String pkg) {
112-
LocaleList locales = context.getResources().getConfiguration().getLocales();
113-
String localeTag = locales.isEmpty() ? "" : locales.get(0).toLanguageTag();
133+
private static String buildLabelCacheKey(String pkg, String localeTag) {
114134
// 首页标题会被应用 label 覆盖,所以缓存必须按 locale 分桶。
115135
return pkg + "#" + localeTag;
116136
}
117137

118-
private static CharSequence loadLabel(Context context, ApplicationInfo info, PackageManager pm) {
138+
private static CharSequence loadLabel(Context context, ApplicationInfo info, PackageManager pm, Locale locale) {
119139
if (info.nonLocalizedLabel != null) {
120140
return info.nonLocalizedLabel;
121141
}
122142
if (info.labelRes != 0) {
123143
try {
124144
Context packageContext = context.createPackageContext(info.packageName, 0);
125-
Configuration configuration = new Configuration(context.getResources().getConfiguration());
145+
Configuration configuration = new Configuration(packageContext.getResources().getConfiguration());
146+
LocaleList localeList = new LocaleList(locale);
147+
configuration.setLocales(localeList);
148+
configuration.setLocale(locale);
126149
// 目标包资源要套用当前页面语言,否则会退回系统语言标题。
127150
Context localizedPackageContext = packageContext.createConfigurationContext(configuration);
128151
CharSequence localized = localizedPackageContext.getText(info.labelRes);
@@ -137,6 +160,14 @@ private static CharSequence loadLabel(Context context, ApplicationInfo info, Pac
137160
return TextUtils.isEmpty(fallback) ? info.packageName : fallback;
138161
}
139162

163+
private static Locale getLocaleSnapshot(Context context) {
164+
return LanguageHelper.getCurrentLocale(context);
165+
}
166+
167+
private static String getLocaleTag(Context context) {
168+
return getLocaleSnapshot(context).toLanguageTag();
169+
}
170+
140171
public interface LoadCallback {
141172
void onReady(AppInfo info);
142173
}

app/src/main/java/com/sevtinge/hyperceiler/home/safemode/CrashActivity.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package com.sevtinge.hyperceiler.home.safemode;
2020

2121
import android.annotation.SuppressLint;
22+
import android.content.Context;
2223
import android.content.Intent;
2324
import android.graphics.Paint;
2425
import android.os.Bundle;
@@ -45,6 +46,11 @@ public class CrashActivity extends AppCompatActivity {
4546
private int throwLineNumber;
4647
private String throwMethodName;
4748

49+
@Override
50+
protected void attachBaseContext(Context newBase) {
51+
super.attachBaseContext(com.sevtinge.hyperceiler.utils.LanguageHelper.wrapContext(newBase));
52+
}
53+
4854
@SuppressLint({"SetTextI18n", "StringFormatInvalid"})
4955
@Override
5056
public void onCreate(@Nullable Bundle bundle) {

app/src/main/java/com/sevtinge/hyperceiler/home/safemode/ExceptionCrashActivity.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ public class ExceptionCrashActivity extends AppCompatActivity implements View.On
4545
private String fullMsg;
4646
private String stackMsg;
4747

48+
@Override
49+
protected void attachBaseContext(Context newBase) {
50+
super.attachBaseContext(com.sevtinge.hyperceiler.utils.LanguageHelper.wrapContext(newBase));
51+
}
52+
4853
@Override
4954
protected void onCreate(@Nullable Bundle savedInstanceState) {
5055
super.onCreate(savedInstanceState);

app/src/main/java/com/sevtinge/hyperceiler/sub/ScopePickerActivity.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.sevtinge.hyperceiler.sub;
22

3+
import android.content.Context;
34
import android.content.pm.PackageManager;
45
import android.graphics.Rect;
56
import android.graphics.drawable.Drawable;
@@ -25,6 +26,7 @@
2526
import com.sevtinge.hyperceiler.model.adapter.AppDataAdapter;
2627
import com.sevtinge.hyperceiler.model.data.AppData;
2728
import com.sevtinge.hyperceiler.model.data.AppDataManager;
29+
import com.sevtinge.hyperceiler.utils.LanguageHelper;
2830
import com.sevtinge.hyperceiler.utils.ScopeManager;
2931
import com.sevtinge.hyperceiler.utils.ThreadUtils;
3032

@@ -71,6 +73,11 @@ public class ScopePickerActivity extends AppCompatActivity
7173

7274
private boolean mIsApplyingScope = false;
7375

76+
@Override
77+
protected void attachBaseContext(Context newBase) {
78+
super.attachBaseContext(LanguageHelper.wrapContext(newBase));
79+
}
80+
7481
@Override
7582
protected void onCreate(@Nullable Bundle savedInstanceState) {
7683
super.onCreate(savedInstanceState);

app/src/main/java/com/sevtinge/hyperceiler/ui/HomePageActivity.java

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66

77
import androidx.annotation.NonNull;
88
import androidx.annotation.Nullable;
9-
import androidx.fragment.app.Fragment;
10-
import androidx.fragment.app.FragmentManager;
11-
import androidx.fragment.app.FragmentTransaction;
129
import androidx.lifecycle.LiveData;
1310
import androidx.preference.Preference;
1411
import androidx.preference.PreferenceFragmentCompat;
@@ -43,7 +40,7 @@ public class HomePageActivity extends AppCompatActivity
4340
implements ActivityCallback, IResult,
4441
PreferenceFragment.OnPreferenceStartFragmentCallback {
4542

46-
private static final String TAG = "HomePageActivity";
43+
private static final String STATE_CURRENT_PAGE = "home_current_page";
4744

4845
public ViewPager mViewPager;
4946
public HomeContentAdapter mContentAdapter;
@@ -68,6 +65,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
6865
AppInitializer.initOnActivityCreate(this, this);
6966
setContentView(R.layout.activity_home);
7067
setupNavigation();
68+
restoreCurrentPage(savedInstanceState);
7169
}
7270

7371
private void setupNavigation() {
@@ -106,38 +104,22 @@ private void rebuildContentPages() {
106104
}
107105

108106
public void reloadPagesForLanguageChange() {
109-
if (mViewPager == null) {
107+
if (isFinishing() || isDestroyed()) {
110108
return;
111109
}
112-
int currentItem = mViewPager.getCurrentItem();
113-
// 首页条目标题会异步按包名覆盖,切语言时先清掉旧 locale 的 label 缓存。
110+
// 首页应用名和页面上下文都依赖当前 locale,直接重建能避免旧 Context 残留。
114111
IconTitleLoader.clearLabelCache();
115-
mViewPager.setAdapter(null);
116-
clearContentFragments();
117-
rebuildContentPages();
118-
if (mSwitchManager != null) {
119-
mSwitchManager.setSelectedPosition(currentItem, false);
120-
}
121-
mViewPager.setCurrentItem(currentItem, false);
112+
recreate();
122113
}
123114

124-
private void clearContentFragments() {
125-
if (mContentAdapter == null) {
115+
private void restoreCurrentPage(@Nullable Bundle savedInstanceState) {
116+
if (savedInstanceState == null || mViewPager == null) {
126117
return;
127118
}
128-
FragmentManager fragmentManager = getSupportFragmentManager();
129-
FragmentTransaction transaction = fragmentManager.beginTransaction();
130-
boolean hasChanges = false;
131-
// 这里必须移除旧 fragment,避免 ViewPager 复用旧实例导致首页列表不刷新语言。
132-
for (int i = 0; i < mContentAdapter.getCount(); i++) {
133-
Fragment fragment = fragmentManager.findFragmentByTag(mContentAdapter.getFragmentTag(i));
134-
if (fragment != null) {
135-
transaction.remove(fragment);
136-
hasChanges = true;
137-
}
138-
}
139-
if (hasChanges) {
140-
transaction.commitNowAllowingStateLoss();
119+
int currentItem = savedInstanceState.getInt(STATE_CURRENT_PAGE, 0);
120+
mViewPager.setCurrentItem(currentItem, false);
121+
if (mSwitchManager != null) {
122+
mSwitchManager.setSelectedPosition(currentItem, false);
141123
}
142124
}
143125

@@ -179,6 +161,14 @@ protected void onPause() {
179161
PageDecorator.onPause();
180162
}
181163

164+
@Override
165+
protected void onSaveInstanceState(@NonNull Bundle outState) {
166+
if (mViewPager != null) {
167+
outState.putInt(STATE_CURRENT_PAGE, mViewPager.getCurrentItem());
168+
}
169+
super.onSaveInstanceState(outState);
170+
}
171+
182172
@Override
183173
public void onDestroy() {
184174
super.onDestroy();
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
package com.sevtinge.hyperceiler.ui;
22

3+
import android.content.Context;
4+
5+
import com.sevtinge.hyperceiler.utils.LanguageHelper;
6+
37
import fan.appcompat.app.AppCompatActivity;
48

5-
public class LauncherActivity extends AppCompatActivity {}
9+
public class LauncherActivity extends AppCompatActivity {
10+
11+
@Override
12+
protected void attachBaseContext(Context newBase) {
13+
super.attachBaseContext(LanguageHelper.wrapContext(newBase));
14+
}
15+
}

library/common/src/main/java/com/sevtinge/hyperceiler/common/base/BaseActivity.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.sevtinge.hyperceiler.common.base;
22

3+
import android.content.Context;
34
import android.os.Bundle;
45
import android.view.Menu;
56
import android.view.MenuItem;
@@ -12,13 +13,19 @@
1213
import androidx.annotation.Nullable;
1314

1415
import com.sevtinge.hyperceiler.common.R;
16+
import com.sevtinge.hyperceiler.common.utils.AppLanguageHelper;
1517

1618
import fan.appcompat.app.AppCompatActivity;
1719

1820
public abstract class BaseActivity extends AppCompatActivity {
1921

2022
protected FrameLayout mContent;
2123

24+
@Override
25+
protected void attachBaseContext(Context newBase) {
26+
super.attachBaseContext(AppLanguageHelper.wrapContext(newBase));
27+
}
28+
2229
@Override
2330
protected void onCreate(@Nullable Bundle savedInstanceState) {
2431
super.onCreate(savedInstanceState);

library/common/src/main/java/com/sevtinge/hyperceiler/common/utils/AppLanguageHelper.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ public static void clearLanguage(Context context) {
9494
updateContextResources(context, getSystemLocale());
9595
}
9696

97+
public static void freezeCurrentLocaleIfUnset(Context context) {
98+
if (hasExplicitLanguage(context)) {
99+
return;
100+
}
101+
AppSettingsStore.setAppLanguageIndex(context, findBestLanguageIndex(getCurrentLocale(context)));
102+
updateContextResources(context, getCurrentLocale(context));
103+
}
104+
97105
public static String getLanguage(Context context) {
98106
Locale locale = getCurrentLocale(context);
99107
String country = locale.getCountry();

library/core/src/main/java/com/sevtinge/hyperceiler/dashboard/base/SettingsBaseActivity.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package com.sevtinge.hyperceiler.dashboard.base;
2020

21+
import android.content.Context;
2122
import android.content.Intent;
2223
import android.os.Bundle;
2324
import android.text.TextUtils;
@@ -28,12 +29,18 @@
2829
import androidx.annotation.Nullable;
2930
import androidx.fragment.app.Fragment;
3031

32+
import com.sevtinge.hyperceiler.common.utils.AppLanguageHelper;
3133
import com.sevtinge.hyperceiler.core.R;
3234

3335
import fan.appcompat.app.AppCompatActivity;
3436

3537
public class SettingsBaseActivity extends AppCompatActivity implements ActivityCallback {
3638

39+
@Override
40+
protected void attachBaseContext(Context newBase) {
41+
super.attachBaseContext(AppLanguageHelper.wrapContext(newBase));
42+
}
43+
3744
@Override
3845
protected void onCreate(@Nullable Bundle savedInstanceState) {
3946
super.onCreate(savedInstanceState);

0 commit comments

Comments
 (0)