Android context.getResources.updateConfiguration()已弃用

kmynzznz  于 2023-10-14  发布在  Android
关注(0)|答案(9)|浏览(135)

就在最近,Android API 25中已弃用context.getResources().updateConfiguration(),建议改用context. updateConfiguration()。
有谁知道如何使用configurationContext来覆盖android系统区域设置?
在此之前,将通过:

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

6ju8rftf1#

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

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

为了注入你的 Package 器,在每个Activity中添加以下代码:

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

更新22/12/2020在Android材质库实现ContextThemeWrapper以支持黑暗模式后,语言设置将中断,语言设置丢失。经过几个月的苦苦思索,通过向Activity和Fragment onCreate方法添加以下代码,问题得到了解决

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”locale Package 上下文。因此,作为一个好的实践,永远不要在Activity或片段的全局变量中保留Context/Activity对象。

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

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

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

kjthegm6

kjthegm62#

大概是这样的:

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

奖励:一篇博客文章,使用了mysql()

2exbekwf

2exbekwf3#

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

然后在Activity的attachBaseContext方法中,简单地将上下文替换为新的上下文。

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

clj7thdc4#

没有100%的工作解决方案。您需要同时使用createConfigurationContextapplyOverrideConfiguration。否则,即使您将每个Activity中的baseContext替换为新配置,Activity仍将使用旧区域设置的ContextThemeWrapper中的Resources
下面是我的解决方案,适用于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,只需将createConfigurationContext返回的新上下文设置为baseContext
  • 这很重要!当你调用createConfigurationContext时,你应该传递配置从头开始,并且只设置Locale属性。不应为该配置设置任何其他属性。因为如果我们为这个配置设置一些其他属性(例如 orientation),我们将永远覆盖这个属性,即使我们旋转屏幕,我们的上下文也不再更改这个 orientation 属性。
  • 当用户选择不同的语言时,仅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)
         }
}
ojsjcaue

ojsjcaue5#

我的灵感来自于书法和我自己,我创造了这个。
首先,你必须创建一个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"
    >

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

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

如果没有设置locale,则硬编码“en”是默认lang

mlmc2os5

mlmc2os56#

以下是@ martel-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))
}
2ul0zpep

2ul0zpep7#

这里有一个简单的contextWrapper解决方案:Android N更改语言时请注意recreate()方法

q5iwbnjs

q5iwbnjs8#

受Bassel Kotlin jan的启发(接受答案),我创建了自己的Kotlin静态函数:

fun Context.setCustomAppLanguage(language: String? = null) {
    val candidateLanguage = language ?: PreferenceManager.getDefaultSharedPreferences(this)
        .getString(LOCALE_KEY, Locale.ENGLISH.language)!!
    val config = resources.configuration
    if (candidateLanguage.isNotBlank() && config.locales[0].language != candidateLanguage) {
        val locale = Locale(candidateLanguage)
        Locale.setDefault(locale)
        config.setLocale(locale)
        val wrappedContext = ContextWrapper(createConfigurationContext(config))
        resources.updateConfiguration(
            wrappedContext.resources.configuration,
            wrappedContext.resources.displayMetrics
        )
    }
}

它可以动态更改语言,并在SDK 34上工作。您需要为每个each上下文调用它,因此我在基础片段(单活动方法)+applicationContext的App类中执行。在执行函数后,你需要使你的UI无效-不要忘记这样做。

2q5ifsrm

2q5ifsrm9#

试试这个:

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

相关问题