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


85

就在最近的context.getResources()中。在Android API 25中已不建议使用updateConfiguration(),建议使用上下文。改为使用createConfigurationContext()

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

在此之前,可以通过:

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

如何applyOverrideConfiguration(未经测试)?
user1480019 '11

也有一个简单的解决方案在这里,非常类似于这样一个stackoverflow.com/questions/39705739/...
Thanasis Saxanidis

[API 25级中不推荐使用updateConfiguration] developer.android.com/reference/android/content/res/Resources
Md Sifatul Islam

Answers:


122

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

    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);
    }
}

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

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

更新23/09/2020 例如,如果覆盖应用主题以应用黑暗模式,则ContextThemeWrapper将破坏语言设置,因此将以下代码添加到您的Activity中以重置所需的语言环境

@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
      Locale locale = new Locale("fr");
      overrideConfiguration.setLocale(locale);
      super.applyOverrideConfiguration(overrideConfiguration);
}

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

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

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

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


5
我对这种方法感到担心...目前仅将其应用于活动,而不是整个应用程序。对于可能不是从活动(例如服务)开始的应用程序组件,将会发生什么?
rfgamaral

6
为什么要扩展ContextWrapper?您没有任何内容,只是静态方法?
vladimir123

7
我必须从if-else分支中取出createConfigurationContext / updateConfiguration并在其下面添加,否则在第一个Activity中一切正常,但是第二个打开时,语言改回了默认设备。找不到原因。
kroky

3
我添加所需的线,并张贴,因为这要点:gist.github.com/muhammad-naderi/...
穆罕默德Naderi

2
@kroky是正确的。系统区域设置已正确更改,但配置恢复为默认设置。结果,字符串资源文件恢复为默认值。是否有任何其他的方式,不是在每个活动设置配置,每次其他
佳日Ladia

29

大概是这样的:

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

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


谢谢您为我指明了正确的方向,我想最终还是必须创建一个ContextWrapper并将其附加到像书法一样的活动上。无论如何,该奖项是您的,但直到我发布正确的解决方法编码后,它才将其视为最终答案。
巴塞尔·穆尔扬

58
API 24 + ...愚蠢的Google,难道他们不只是为我们提供一种简单的方法吗?
抗体

7
@click_whir说“如果仅将这些设备作为目标,这很简单”实际上并没有使它变得简单。
Vlad

1
@Vlad如果您不需要支持2012年之前生产的设备,则有一个简单的方法。欢迎您进行应用程序开发!
click_whir

1
您从哪里获得LocaleList
EdgeDev

4

受书法和穆尔詹本人的启发,我创建了这个。

首先,您必须创建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());
        }
    }
}

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

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

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

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

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

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

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


这还不够,您还需要在每个活动中覆盖上下文。最终,您的情况是baseContext具有一个语言环境,而您的应用程序将具有另一个语言环境。结果,您的ui中会出现多种语言。请看我的回答。
Oleksandr Albul

3

这里没有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")!!
             )
        )
    }
}

同样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”是您输入用户当前语言的首选键。
  • 如果上下文已经使用所需的语言环境,则无需重新创建上下文。
  • 没有必要ContextWraper在这里发布,只需设置从createConfigurationContextbaseContext返回的新上下文即可
  • 这个非常重要!致电时,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)
         }
}

这是行不通的。还可以考虑为所有活动创建基本活动,而不是在每个活动中都重复代码。同样,该recreateTask(Context context)方法无法正常工作,因为我仍然看到布局没有任何更改。
blueware

@blueware我已经更新了示例。以前有一些错误。但目前应该可以使用,这是我的生产应用程序中的代码。乐趣recreateTask不能工作,你可以显示像“语言将重启后改为”干杯..
阿列克Albul

1

这是@ bassel-mourjan的解决方案,带有一点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))
}

这条线val config = baseContext.resources.configuration是非常非常错误的。因此,您最终会遇到许多错误。您需要创建新配置。看我的答案。
Oleksandr Albul


-1

试试这个:

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

1
它只是创建您需要用新的上下文切换的活动
Ali Karaca
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.