Android context.getResources.updateConfiguration() deprecated

后端 未结 7 607
死守一世寂寞
死守一世寂寞 2020-11-22 16:05

Just recently context.getResources().updateConfiguration() has been deprecated in Android API 25 and it is advised to use context.createConfigurationContext() instead.

相关标签:
7条回答
  • 2020-11-22 16:42

    Here's @bassel-mourjan's solution with a bit of kotlin goodness :) :

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

    And here is how you use it:

    override fun attachBaseContext(newBase: Context?) {
        super.attachBaseContext(ContextWrapper(newBase).wrap(defaultLocale.language))
    }
    
    0 讨论(0)
  • Try this:

    Configuration config = getBaseContext().getResources().getConfiguration();
    config.setLocale(locale);
    context.createConfigurationContext(config);
    
    0 讨论(0)
  • 2020-11-22 16:45

    Probably like this :

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

    Bonus : A blog article who use createConfigurationContext()

    0 讨论(0)
  • 2020-11-22 16:55

    Inspired by Calligraphy & Mourjan & myself, i created this.

    first you must create a subclass of 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"
        >
    

    note that pref_locale is a string resource like this:

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

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

    0 讨论(0)
  • 2020-11-22 16:56

    there is a simple solution with contextWrapper here : Android N change language programatically Pay attention to the recreate() method

    0 讨论(0)
  • 2020-11-22 17:07

    Here is no 100% working solution. You need to use both createConfigurationContext and applyOverrideConfiguration. Otherwise even if you replace baseContext in every activity with new configuration, activity would still use Resources from ContextThemeWrapper with old locale.

    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")!!
                 )
            )
        }
    }
    

    Also every Activity from:

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

    Add LocaleExt.kt with next extension functions:

    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
        }
    

    Add to your res/values/arrays.xml your supported languages in array:

    <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>
    

    I want to mention:

    • Use config.setLayoutDirection(toLocale); to change layout direction when you use RTL locales like Arabic, Persian, etc.
    • "sys" in the code is a value that means "inherit system default language".
    • Here "langPref" is a key of preference where you put user current language.
    • There is no need to recreate the context if it already uses needed locale.
    • There is no need for ContextWraper as posted here, just set new context returned from createConfigurationContext as baseContext
    • This is very important! When you call createConfigurationContext you should pass configuration crated from scratch and only with Locale property set. There shouldn't be any other property set to this configuration. Because if we set some other properties for this config (orientation for example), we override that property forever, and our context no longer change this orientation property even if we rotate the screen.
    • It is not enough only to recreate activity when user selects a different language, because applicationContext will remain with old locale and it could provide unexpected behaviour. So listen to preference change and restart whole application task instead:

    fun Context.recreateTask() {
        this.packageManager
            .getLaunchIntentForPackage(context.packageName)
            ?.let { intent ->
                val restartIntent = Intent.makeRestartActivityTask(intent.component)
                this.startActivity(restartIntent)
                Runtime.getRuntime().exit(0)
             }
    }
    
    0 讨论(0)
提交回复
热议问题