Android context.getResources.updateConfiguration() deprecated

后端 未结 7 695
死守一世寂寞
死守一世寂寞 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 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:

    
        sys 
        ar
        de
        en
        es
        fa
        ...
        zh 
        zh-rCN 
    
    

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

提交回复
热议问题