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

Bas*_*jan 70 android locale right-to-left

就在最近的context.getResources().updateConfiguration()已在Android API 25中弃用,建议使用上下文.而是createConfigurationContext().

有谁知道createConfigurationContext如何用于覆盖android系统区域设置?

在此之前将通过以下方式完成:

Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
                                 context.getResources().getDisplayMetrics());
Run Code Online (Sandbox Code Playgroud)

Bas*_*jan 103

书法的启发,我最终创建了一个上下文包装器.在我的情况下,我需要覆盖系统语言,为我的应用程序用户提供更改应用程序语言的选项,但这可以使用您需要实现的任何逻辑进行自定义.

    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.JELLY_BEAN_MR1) {
             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);
    }
}
Run Code Online (Sandbox Code Playgroud)

并注入您的包装器,在每个活动中添加以下代码:

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr"));
}
Run Code Online (Sandbox Code Playgroud)

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

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

public Context getMyContext(){
    return MyContextWrapper.wrap(getContext(),"fr");
}
Run Code Online (Sandbox Code Playgroud)

这种做法将为您提供100%无错误的解决方案.

  • 我不得不从if-else分支中取出createConfigurationContext/updateConfiguration并在其下面添加,否则在第一个Activity中一切正常,但是当第二次打开时,语言变回设备默认值.找不到原因. (7认同)
  • 我对此方法有一个顾虑......目前仅适用于活动,而不是整个应用程序.对于可能无法从服务等活动开始的应用程序组件会发生什么? (4认同)
  • 你为什么要扩展ContextWrapper?你没有任何东西,只有静态方法? (4认同)
  • 我添加了所需的行并将其作为这个要点发布:https://gist.github.com/muhammad-naderi/0ff264e6cc07df904bc88a9f7efbe57d (3认同)
  • @kroky是对的.系统区域设置已正确更改,但配置将恢复为默认值.结果,字符串资源文件恢复为默认值.除了每次活动中的每次设置配置外,还有其他方法吗? (2认同)
  • @ dor506很简单,你所要做的就是覆盖attachBaseContext,如下所示:super.attachBaseContext(CalligraphyContextWrapper.wrap(MyContextWrapper.wrap(newBase,"fr"))); (2认同)
  • 我有一个问题:使用`attachBaseContext` locale的第二个活动后无法再改变.解决方案:我删除了检查当前区域设置的条件.所以`get`方法毫无用处.这就是诀窍. (2认同)

com*_*879 24

可能是这样的:

Configuration overrideConfiguration = getBaseContext().getResources().getConfiguration();
overrideConfiguration.setLocales(LocaleList);
Context context  = createConfigurationContext(overrideConfiguration);
Resources resources = context.getResources();
Run Code Online (Sandbox Code Playgroud)

奖励:使用createConfigurationContext()的博客文章

  • API 24 + ...愚蠢的谷歌,他们不能只为我们提供一个简单的方法吗? (39认同)
  • @click_whir说"如果你只针对这些设备这很简单"并不能让它变得简单. (6认同)

Cod*_*meo 24

我已经解决了这个问题,没有创建任何自定义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)
}
Run Code Online (Sandbox Code Playgroud)

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

override fun attachBaseContext(newBase: Context) {
  super.attachBaseContext(ContextWrapper(newBase.setAppLocale("bn")))
}
Run Code Online (Sandbox Code Playgroud)

  • 这应该是 2022 年最好的答案。谢谢。 (4认同)

Mah*_*oya 6

受书法、Mourjan 和我自己的启发,我创造了这个。

首先你必须创建一个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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

那么您需要将其设置为您的 AndroidManifest.xml 应用程序标记:

<application
    ...
    android:name="path.to.your.package.MyApplication"
    >
Run Code Online (Sandbox Code Playgroud)

并将其添加到您的 AndroidManifest.xml 活动标记中。

<activity
    ...
    android:configChanges="locale"
    >
Run Code Online (Sandbox Code Playgroud)

请注意, pref_locale 是这样的字符串资源:

<string name="pref_locale">fa</string>
Run Code Online (Sandbox Code Playgroud)

如果未设置 pref_locale,则硬编码“en”是默认语言


Ole*_*bul 6

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

这是我的解决方案,适用于 API 29:

从以下类别对您的MainApplication班级进行子类化:

abstract class LocalApplication : Application() {

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

还有来自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)
    }
}
Run Code Online (Sandbox Code Playgroud)

添加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
    }
Run Code Online (Sandbox Code Playgroud)

添加到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>
Run Code Online (Sandbox Code Playgroud)

我想提一下:

  • config.setLayoutDirection(toLocale);当您使用阿拉伯语、波斯语等 RTL 语言环境时,用于更改布局方向。
  • "sys"代码中的值表示“继承系统默认语言”。
  • 这里的“langPref”是您放置用户当前语言的偏好键。
  • 如果上下文已使用所需的区域设置,则无需重新创建上下文。
  • 不需要像这里发布的那样,只需设置从baseContextContextWraper返回的新上下文createConfigurationContext
  • 这个非常重要!当您调用时,createConfigurationContext您应该传递从头开始创建的配置,并且仅包含Locale属性集。不应为此配置设置任何其他属性。因为如果我们为此配置设置一些其他属性(例如方向),我们将永远覆盖该属性,并且即使我们旋转屏幕,我们的上下文也不再更改该方向属性。
  • 仅当用户选择不同的语言时进行活动是不够的recreate,因为 applicationContext 将保留旧的语言环境,并且可能会提供意外的行为。因此,监听首选项更改并重新启动整个应用程序任务:

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