不推荐使用 Android context.getResources.updateConfiguration()

最近,在 Android API 25中已经不推荐使用 context.getResources () . UpdateConfiguration (),建议使用 context.CreateConfigurationContext ()

有人知道如何使用 创建配置上下文覆盖 android 系统的语言环境吗?

在此之前,将通过:

Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
context.getResources().getDisplayMetrics());
88523 次浏览

试试这个:

Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.createConfigurationContext(config);

可能是这样的:

Configuration overrideConfiguration = getBaseContext().getResources().getConfiguration();
overrideConfiguration.setLocales(LocaleList);
Context context  = createConfigurationContext(overrideConfiguration);
Resources resources = context.getResources();

意外收获: 使用 createConfigurationContext ()的博客文章

受到 书法的启发,我最终创建了一个上下文包装器。 在我的例子中,我需要覆盖系统语言来为我的应用程序用户提供更改应用程序语言的选项,但是这可以用您需要实现的任何逻辑来定制。

    import android.annotation.TargetApi;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Build;
    

import java.util.Locale;
    

public class MyContextWrapper extends ContextWrapper {


public MyContextWrapper(Context base) {
super(base);
}


@SuppressWarnings("deprecation")
public static ContextWrapper wrap(Context context, String language) {
Configuration config = context.getResources().getConfiguration();
Locale sysLocale = null;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
sysLocale = getSystemLocale(config);
} else {
sysLocale = getSystemLocaleLegacy(config);
}
if (!language.equals("") && !sysLocale.getLanguage().equals(language)) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSystemLocale(config, locale);
} else {
setSystemLocaleLegacy(config, locale);
}
            

}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context = context.createConfigurationContext(config);
} else {
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
}
return new MyContextWrapper(context);
}


@SuppressWarnings("deprecation")
public static Locale getSystemLocaleLegacy(Configuration config){
return config.locale;
}


@TargetApi(Build.VERSION_CODES.N)
public static Locale getSystemLocale(Configuration config){
return config.getLocales().get(0);
}


@SuppressWarnings("deprecation")
public static void setSystemLocaleLegacy(Configuration config, Locale locale){
config.locale = locale;
}


@TargetApi(Build.VERSION_CODES.N)
public static void setSystemLocale(Configuration config, Locale locale){
config.setLocale(locale);
}
}

and to inject your wrapper, in every activity add the following code:

@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr"));
}

更新日期22/12/2020 After android Material library implementation of ContextThemeWrapper to support dark mode, the language setting would break and language setting is lost. After months of head scratching, problem was resolved by adding the following code to Activity and Fragment onCreate method

Context context = MyContextWrapper.wrap(this/*in fragment use getContext() instead of this*/, "fr");
getResources().updateConfiguration(context.getResources().getConfiguration(), context.getResources().getDisplayMetrics());

更新日期: 2018年10月19日 有时在方向改变,或者活动暂停/恢复之后,Configuration 对象重置为默认的系统配置,结果我们会看到应用程序显示英语“ en”文本,即使我们用法语“ fr”语言环境包装了上下文。因此,作为一个好的实践,永远不要在活动或片段中将 Context/Activity 对象保留在全局变量中。

此外,在 MyBaseFragment 或 MyBaseActivity 中创建并使用以下内容:

public Context getMyContext(){
return MyContextWrapper.wrap(getContext(),"fr");
}

此实践将为您提供100% 的无错误解决方案。

这里有一个使用 contextWrapper 的简单解决方案: < a href = “ https://stackoverflow. com/questions/39705739/Android-n-change-language-Programated/40849142 # 40849142”> Android N change language-Programated Pay attention to the recreate() method

灵感来自书法,穆里安和我自己,我创造了这个。

首先,您必须创建 Application 的子类:

public class MyApplication extends Application {
private Locale locale = null;


@Override
public void onCreate() {
super.onCreate();


SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);


Configuration config = getBaseContext().getResources().getConfiguration();


String lang = preferences.getString(getString(R.string.pref_locale), "en");
String systemLocale = getSystemLocale(config).getLanguage();
if (!"".equals(lang) && !systemLocale.equals(lang)) {
locale = new Locale(lang);
Locale.setDefault(locale);
setSystemLocale(config, locale);
updateConfiguration(config);
}
}


@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (locale != null) {
setSystemLocale(newConfig, locale);
Locale.setDefault(locale);
updateConfiguration(newConfig);
}
}


@SuppressWarnings("deprecation")
private static Locale getSystemLocale(Configuration config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return config.getLocales().get(0);
} else {
return config.locale;
}
}


@SuppressWarnings("deprecation")
private static void setSystemLocale(Configuration config, Locale locale) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(locale);
} else {
config.locale = locale;
}
}


@SuppressWarnings("deprecation")
private void updateConfiguration(Configuration config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
getBaseContext().createConfigurationContext(config);
} else {
getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
}
}
}

then you need set this to your AndroidManifest.xml application tag:

<application
...
android:name="path.to.your.package.MyApplication"
>

and add this to your AndroidManifest.xml activity tag.

<activity
...
android:configChanges="locale"
>

请注意,pref _ locale 是这样的字符串资源:

<string name="pref_locale">fa</string>

and hardcode "en" is default lang if pref_locale is not setted

以下是@bassel-moujan 的解决方案,其中包含了一些 kotlin 善意:):

import android.annotation.TargetApi
import android.content.ContextWrapper
import android.os.Build
import java.util.*


@Suppress("DEPRECATION")
fun ContextWrapper.wrap(language: String): ContextWrapper {
val config = baseContext.resources.configuration
val sysLocale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.getSystemLocale()
} else {
this.getSystemLocaleLegacy()
}


if (!language.isEmpty() && sysLocale.language != language) {
val locale = Locale(language)
Locale.setDefault(locale)


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.setSystemLocale(locale)
} else {
this.setSystemLocaleLegacy(locale)
}
}


return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
val context = baseContext.createConfigurationContext(config)
ContextWrapper(context)
} else {
baseContext.resources.updateConfiguration(config, baseContext.resources.displayMetrics)
ContextWrapper(baseContext)
}


}


@Suppress("DEPRECATION")
fun ContextWrapper.getSystemLocaleLegacy(): Locale {
val config = baseContext.resources.configuration
return config.locale
}


@TargetApi(Build.VERSION_CODES.N)
fun ContextWrapper.getSystemLocale(): Locale {
val config = baseContext.resources.configuration
return config.locales[0]
}




@Suppress("DEPRECATION")
fun ContextWrapper.setSystemLocaleLegacy(locale: Locale) {
val config = baseContext.resources.configuration
config.locale = locale
}


@TargetApi(Build.VERSION_CODES.N)
fun ContextWrapper.setSystemLocale(locale: Locale) {
val config = baseContext.resources.configuration
config.setLocale(locale)
}

以下是你如何使用它:

override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(ContextWrapper(newBase).wrap(defaultLocale.language))
}

这里没有100% 可行的解决方案。您需要同时使用 createConfigurationContextapplyOverrideConfiguration。否则,即使您在每个活动中使用新配置替换 baseContext,活动仍将使用 ContextThemeWrapper中的 Resources和旧的区域设置。

So here is mine solution which works up to API 29:

Subclass your MainApplication class from:

abstract class LocalApplication : Application() {


override fun attachBaseContext(base: Context) {
super.attachBaseContext(
base.toLangIfDiff(
PreferenceManager
.getDefaultSharedPreferences(base)
.getString("langPref", "sys")!!
)
)
}
}

每个 Activity都来自:

abstract class LocalActivity : AppCompatActivity() {


override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(
PreferenceManager
.getDefaultSharedPreferences(base)
.getString("langPref", "sys")!!
)
}


override fun applyOverrideConfiguration(overrideConfiguration: Configuration) {
super.applyOverrideConfiguration(baseContext.resources.configuration)
}
}

添加 LocaleExt.kt和下一个扩展函数:

const val SYSTEM_LANG = "sys"
const val ZH_LANG = "zh"
const val SIMPLIFIED_CHINESE_SUFFIX = "rCN"




private fun Context.isAppLangDiff(prefLang: String): Boolean {
val appConfig: Configuration = this.resources.configuration
val sysConfig: Configuration = Resources.getSystem().configuration


val appLang: String = appConfig.localeCompat.language
val sysLang: String = sysConfig.localeCompat.language


return if (SYSTEM_LANG == prefLang) {
appLang != sysLang
} else {
appLang != prefLang
|| ZH_LANG == prefLang
}
}


fun Context.toLangIfDiff(lang: String): Context =
if (this.isAppLangDiff(lang)) {
this.toLang(lang)
} else {
this
}


@Suppress("DEPRECATION")
fun Context.toLang(toLang: String): Context {
val config = Configuration()


val toLocale = langToLocale(toLang)


Locale.setDefault(toLocale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(toLocale)


val localeList = LocaleList(toLocale)
LocaleList.setDefault(localeList)
config.setLocales(localeList)
} else {
config.locale = toLocale
}


return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLayoutDirection(toLocale)
this.createConfigurationContext(config)
} else {
this.resources.updateConfiguration(config, this.resources.displayMetrics)
this
}
}


/**
* @param toLang - two character representation of language, could be "sys" - which represents system's locale
*/
fun langToLocale(toLang: String): Locale =
when {
toLang == SYSTEM_LANG ->
Resources.getSystem().configuration.localeCompat


toLang.contains(ZH_LANG) -> when {
toLang.contains(SIMPLIFIED_CHINESE_SUFFIX) ->
Locale.SIMPLIFIED_CHINESE
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ->
Locale(ZH_LANG, "Hant")
else ->
Locale.TRADITIONAL_CHINESE
}


else -> Locale(toLang)
}


@Suppress("DEPRECATION")
private val Configuration.localeCompat: Locale
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.locales.get(0)
} else {
this.locale
}

res/values/arrays.xml中以数组形式添加支持的语言:

<string-array name="lang_values" translatable="false">
<item>sys</item> <!-- System default -->
<item>ar</item>
<item>de</item>
<item>en</item>
<item>es</item>
<item>fa</item>
...
<item>zh</item> <!-- Traditional Chinese -->
<item>zh-rCN</item> <!-- Simplified Chinese -->
</string-array>

我想提一下:

  • 使用 config.setLayoutDirection(toLocale);更改布局方向 当您使用 RTL 语言环境时,如阿拉伯语、波斯语等。
  • 代码中的 "sys"是一个表示“继承系统默认语言”的值。
  • 在这里,“ langPref”是放置用户当前语言的首选项。
  • There is no need to recreate the context if it already uses needed 地点。
  • 这里不需要 ContextWraper,只需将从 createConfigurationContext返回的新上下文设置为 baseContext
  • 这很重要!当您调用 createConfigurationContext时,您应该传递配置 从零开始装箱并且只设置 Locale属性。不应该有任何其他属性设置为此配置。因为如果我们为这个配置设置一些其他属性(例如 定位) ,我们将永远重写这个属性,即使我们旋转屏幕,我们的上下文也不再更改这个 定位属性。
  • 当用户选择不同的语言时,仅仅进行 recreate活动是不够的,因为 applicationContext 将保留旧的 locale,并且可能提供意外的行为。因此,听取首选项更改并重新启动整个应用程序任务:

fun Context.recreateTask() {
this.packageManager
.getLaunchIntentForPackage(context.packageName)
?.let { intent ->
val restartIntent = Intent.makeRestartActivityTask(intent.component)
this.startActivity(restartIntent)
Runtime.getRuntime().exit(0)
}
}

我在没有创建任何自定义 ContextWrapper的情况下解决了这个问题。

首先我创建了一个扩展函数

fun Context.setAppLocale(language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val config = resources.configuration
config.setLocale(locale)
config.setLayoutDirection(locale)
return createConfigurationContext(config)
}

然后在活动的 attachBaseContext方法中,只需用新的上下文替换上下文。

override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(ContextWrapper(newBase.setAppLocale("bn")))
}