在Android应用程序中更改语言环境从来都不是一件容易的事情。使用androidx.appcompat:appcompat:1.3.0-alpha02
,似乎在应用程序中更改语言环境比我想象的要困难得多。它似乎表明活动上下文和应用程序上下文的行为非常不同。如果我使用一个通用的BaseActivity
(如下所示)更改活动的语言环境,则会对相应的活动起作用。
BaseActivity.java
public class BaseActivity extends AppCompatActivity {
private Locale currentLocale;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
currentLocale = LangUtils.updateLanguage(this);
super.onCreate(savedInstanceState);
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(LangUtils.attachBaseContext(newBase));
}
@Override
protected void onResume() {
super.onResume();
if (currentLocale != LangUtils.getLocaleByLanguage(this)) recreate();
}
}
但是我需要更改应用程序上下文的区域设置,因为这仅限于活动。为此,我可以轻松地重写 Application#attachBaseContext()
来更新区域设置,就像上面所述。
MyApplication.java
public class MyApplication extends Application {
private static MyApplication instance;
@NonNull
public static MyApplication getInstance() {
return instance;
}
@NonNull
public static Context getContext() {
return instance.getBaseContext();
}
@Override
public void onCreate() {
instance = this;
super.onCreate();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LangUtils.attachBaseContext(base));
}
}
这样做虽然成功地改变了应用程序上下文的语言环境,但活动上下文不再遵循自定义语言环境(无论我是否从BaseActivity
扩展每个活动)。奇怪。
LangUtils.java
public final class LangUtils {
public static final String LANG_AUTO = "auto";
private static Map<String, Locale> sLocaleMap;
private static Locale sDefaultLocale;
static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sDefaultLocale = LocaleList.getDefault().get(0);
} else sDefaultLocale = Locale.getDefault();
}
public static Locale updateLanguage(@NonNull Context context) {
Resources resources = context.getResources();
Configuration config = resources.getConfiguration();
Locale currentLocale = getLocaleByLanguage(context);
config.setLocale(currentLocale);
DisplayMetrics dm = resources.getDisplayMetrics();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N){
context.getApplicationContext().createConfigurationContext(config);
} else {
resources.updateConfiguration(config, dm);
}
return currentLocale;
}
public static Locale getLocaleByLanguage(Context context) {
// Get language from shared preferences
String language = AppPref.getNewInstance(context).getString(AppPref.PrefKey.PREF_CUSTOM_LOCALE_STR);
if (sLocaleMap == null) {
String[] languages = context.getResources().getStringArray(R.array.languages_key);
sLocaleMap = new HashMap<>(languages.length);
for (String lang : languages) {
if (LANG_AUTO.equals(lang)) {
sLocaleMap.put(LANG_AUTO, sDefaultLocale);
} else {
String[] langComponents = lang.split("-", 2);
if (langComponents.length == 1) {
sLocaleMap.put(lang, new Locale(langComponents[0]));
} else if (langComponents.length == 2) {
sLocaleMap.put(lang, new Locale(langComponents[0], langComponents[1]));
} else {
Log.d("LangUtils", "Invalid language: " + lang);
sLocaleMap.put(LANG_AUTO, sDefaultLocale);
}
}
}
}
Locale locale = sLocaleMap.get(language);
return locale != null ? locale : sDefaultLocale;
}
public static Context attachBaseContext(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResources(context);
} else {
return context;
}
}
@TargetApi(Build.VERSION_CODES.N)
private static Context updateResources(@NonNull Context context) {
Resources resources = context.getResources();
Locale locale = getLocaleByLanguage(context);
Configuration configuration = resources.getConfiguration();
configuration.setLocale(locale);
configuration.setLocales(new LocaleList(locale));
return context.createConfigurationContext(configuration);
}
}
因此,我的结论是:
- 如果在应用程序上下文中设置了locale,则无论是否设置了活动上下文,locale都将仅设置为应用程序上下文,而不是活动(或任何其他)上下文。
- 如果在应用程序上下文中未设置locale但在活动上下文中设置了,则会将locale设置为活动上下文。
- 在活动上下文中设置语言环境并在所有地方使用它。但是,如果没有打开的活动,则通知等功能将无法工作。
- 在应用程序上下文中设置语言环境并在所有地方使用它。但这意味着您不能利用
Context#getResources()
来获取活动的资源。
ContextWrapper
。我尝试过使用它,但是问题依旧。一旦我使用上下文包装器包装应用程序上下文,活动和片段的locale就停止工作了。
public class MyContextWrapper extends ContextWrapper {
public MyContextWrapper(Context base) {
super(base);
}
@NonNull
public static ContextWrapper wrap(@NonNull Context context) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
Locale locale = LangUtils.getLocaleByLanguage(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(locale);
LocaleList localeList = new LocaleList(locale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
} else {
configuration.setLocale(locale);
DisplayMetrics dm = res.getDisplayMetrics();
res.updateConfiguration(configuration, dm);
}
configuration.setLayoutDirection(locale);
context = context.createConfigurationContext(configuration);
return new MyContextWrapper(context);
}
}
AppCompatDelegate.setApplicationLocales(locale)
API仅适用于活动上下文:https://issuetracker.google.com/issues/243457462。 - digrecAppCompatDelegate
的许多功能,因为为一个本应帮助开发人员的类编写解决方法并没有真正起到帮助作用。您可以在这里找到它:https://github.com/MuntashirAkon/AppManager/blob/master/app/src/main/java/io/github/muntashirakon/AppManager/utils/appearance/AppearanceUtils.java。如果有任何人需要,我可以添加可允许的许可证。 - Muntashir AkonAppCompatDelegate
来更改语言环境并在用户登录时重新创建活动(当用户的语言变更时),并将其与您的应用程序上下文配置更新(来自此答案)相结合。幸运的是,我不需要主题或方向配置,只需要语言环境。 - digrec