问题描述
在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 AppManager instance;
@NonNull
public static AppManager 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);
}
}
因此,我的结论是:
- 如果在应用程序上下文中设置了语言环境,则无论您是否设置活动上下文,都将仅将语言环境设置为应用程序上下文,而不是活动(或任何其他)上下文。
- 如果不是在应用程序上下文中设置语言环境,而是在活动上下文中设置语言环境,则该语言环境将设置为活动上下文。
我能想到的解决方法是:
- 在活动上下文中设置区域设置,并在各处使用它们。但是,如果没有任何打开的活动,则通知等将不起作用。
- 在应用程序上下文中设置语言环境,并在各处使用它。但这意味着您无法利用
Context#getResources()
进行活动。
编辑(2020年10月30日):有些人建议使用Contextwrapper
。我尝试使用一个(如下所示),但仍然是相同的问题。一旦使用上下文包装器包装了应用程序上下文,语言环境就停止为活动和片段工作。没什么改变。
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);
}
}
解决方法
博客文章how to change the language on Android at runtime and don’t go mad解决了这个问题(以及其他问题),作者创建了一个名为Lingver的库来解决问题。
LangUtils.java
public final class LangUtils {
public static final String LANG_AUTO = "auto";
private static Map<String,Locale> sLocaleMap;
private static final Locale sDefaultLocale;
static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sDefaultLocale = LocaleList.getDefault().get(0);
} else sDefaultLocale = Locale.getDefault();
}
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 {
sLocaleMap.put(lang,Locale.forLanguageTag(lang));
}
}
}
Locale locale = sLocaleMap.get(language);
return locale != null ? locale : sDefaultLocale;
}
}
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();
Lingver.init(instance,LangUtils.getLocaleByLanguage(instance));
}
}
在您要更改语言的首选项中,可以添加以下内容:
// Set new language to the shared preferences
AppPref.set(AppPref.PrefKey.PREF_CUSTOM_LOCALE_STR,currentLang);
// Apply the language
Lingver.getInstance().setLocale(activity,LangUtils.getLocaleByLanguage(activity));
// Recreate the current activity
activity.recreate();