android - 迁移到 Androidx 后更改区域设置不起作用

标签 android android-activity android-support-library androidx

我有一个支持多语言的旧项目。我要升级支持库和目标平台,在迁移到 Androidx 之前一切正常,但现在更改语言不起作用!

我使用此代码更改 App 的默认语言环境

private static Context updateResources(Context context, String language)
{
    Locale locale = new Locale(language);
    Locale.setDefault(locale);

    Configuration configuration = context.getResources().getConfiguration();
    configuration.setLocale(locale);

    return context.createConfigurationContext(configuration);
}

并通过覆盖 attachBaseContext 在每个 Activity 上调用此方法像这样:
@Override
protected void attachBaseContext(Context newBase)
{
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
    String language = preferences.getString(SELECTED_LANGUAGE, "fa");
    super.attachBaseContext(updateResources(newBase, language));
}

我尝试了其他方法来获取字符串,我注意到 ‍‍‍‍ getActivity().getBaseContext().getString工作和getActivity().getString不行。甚至下面的代码也不起作用并且总是显示app_name默认资源 string.xml 中的值。
<TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/app_name"/>

我在 https://github.com/Freydoonk/LanguageTest 中分享了一个示例代码

还有getActivity()..getResources().getIdentifier不起作用,总是返回 0!

最佳答案

2020 年 8 月 21 日更新:
AppCompat 1.2.0 终于发布了。如果您不使用 ContextWrapperContextThemeWrapper根本没有其他事情可做,您应该能够从 1.1.0 中删除任何解决方法!
如果您确实使用 ContextWrapperContextThemeWrapper里面 attachBaseContext , 语言环境更改将中断,因为当您将包装的上下文传递给 super 时,

  • 1.2.0 AppCompatActivity进行内部调用,将您的 ContextWrapper 包装起来在另一个 ContextThemeWrapper ,
  • 或者如果您使用 ContextThemeWrapper ,将其配置覆盖为空白,类似于 1.1.0 中发生的情况。

  • 但解决方案总是一样的。对于情况 2,我尝试了多种其他解决方案,但正如 @Kreiri 在评论中指出的那样(感谢您的调查帮助!),AppCompatDelegateImpl总是以剥离语言环境而告终。最大的障碍是,与 1.1.0 不同,applyOverrideConfiguration在您的基本上下文而不是您的主机 Activity 上调用,因此您不能像在 1.1.0 中那样在 Activity 中覆盖该方法并修复语言环境。我知道的唯一可行的解​​决方案是通过覆盖 getDelegate() 来反转包装。确保您的包装和/或语言环境覆盖在最后。首先,添加下面的类:
    Kotlin 示例(请注意,该类必须位于 androidx.appcompat.app 包内,因为唯一现有的 AppCompatDelegate 构造函数是包私有(private)的)
    package androidx.appcompat.app
    
    import android.content.Context
    import android.content.res.Configuration
    import android.os.Bundle
    import android.util.AttributeSet
    import android.view.MenuInflater
    import android.view.View
    import android.view.ViewGroup
    import androidx.appcompat.view.ActionMode
    import androidx.appcompat.widget.Toolbar
    
    class BaseContextWrappingDelegate(private val superDelegate: AppCompatDelegate) : AppCompatDelegate() {
    
        override fun getSupportActionBar() = superDelegate.supportActionBar
    
        override fun setSupportActionBar(toolbar: Toolbar?) = superDelegate.setSupportActionBar(toolbar)
    
        override fun getMenuInflater(): MenuInflater? = superDelegate.menuInflater
    
        override fun onCreate(savedInstanceState: Bundle?) {
            superDelegate.onCreate(savedInstanceState)
            removeActivityDelegate(superDelegate)
            addActiveDelegate(this)
        }
    
        override fun onPostCreate(savedInstanceState: Bundle?) = superDelegate.onPostCreate(savedInstanceState)
    
        override fun onConfigurationChanged(newConfig: Configuration?) = superDelegate.onConfigurationChanged(newConfig)
    
        override fun onStart() = superDelegate.onStart()
    
        override fun onStop() = superDelegate.onStop()
    
        override fun onPostResume() = superDelegate.onPostResume()
    
        override fun setTheme(themeResId: Int) = superDelegate.setTheme(themeResId)
    
        override fun <T : View?> findViewById(id: Int) = superDelegate.findViewById<T>(id)
    
        override fun setContentView(v: View?) = superDelegate.setContentView(v)
    
        override fun setContentView(resId: Int) = superDelegate.setContentView(resId)
    
        override fun setContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.setContentView(v, lp)
    
        override fun addContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.addContentView(v, lp)
    
        override fun attachBaseContext2(context: Context) = wrap(superDelegate.attachBaseContext2(super.attachBaseContext2(context)))
    
        override fun setTitle(title: CharSequence?) = superDelegate.setTitle(title)
    
        override fun invalidateOptionsMenu() = superDelegate.invalidateOptionsMenu()
    
        override fun onDestroy() {
            superDelegate.onDestroy()
            removeActivityDelegate(this)
        }
    
        override fun getDrawerToggleDelegate() = superDelegate.drawerToggleDelegate
    
        override fun requestWindowFeature(featureId: Int) = superDelegate.requestWindowFeature(featureId)
    
        override fun hasWindowFeature(featureId: Int) = superDelegate.hasWindowFeature(featureId)
    
        override fun startSupportActionMode(callback: ActionMode.Callback) = superDelegate.startSupportActionMode(callback)
    
        override fun installViewFactory() = superDelegate.installViewFactory()
    
        override fun createView(parent: View?, name: String?, context: Context, attrs: AttributeSet): View? = superDelegate.createView(parent, name, context, attrs)
    
        override fun setHandleNativeActionModesEnabled(enabled: Boolean) {
            superDelegate.isHandleNativeActionModesEnabled = enabled
        }
    
        override fun isHandleNativeActionModesEnabled() = superDelegate.isHandleNativeActionModesEnabled
    
        override fun onSaveInstanceState(outState: Bundle?) = superDelegate.onSaveInstanceState(outState)
    
        override fun applyDayNight() = superDelegate.applyDayNight()
    
        override fun setLocalNightMode(mode: Int) {
            superDelegate.localNightMode = mode
        }
    
        override fun getLocalNightMode() = superDelegate.localNightMode
    
        private fun wrap(context: Context): Context {
            TODO("your wrapping implementation here")
        }
    }
    
    然后在我们的基本 Activity 类中删除所有 1.1.0 解决方法并简单地添加:
    private var baseContextWrappingDelegate: AppCompatDelegate? = null
    
    override fun getDelegate() = baseContextWrappingDelegate ?: BaseContextWrappingDelegate(super.getDelegate()).apply {
        baseContextWrappingDelegate = this
    }
    
    取决于 ContextWrapper您正在使用的实现,配置更改可能会破坏主题或语言环境的更改。要解决此问题,请另外添加以下内容:
    override fun createConfigurationContext(overrideConfiguration: Configuration) : Context {
        val context = super.createConfigurationContext(overrideConfiguration)
        TODO("your wrapping implementation here")
    }
    
    而且你很好!您可以期待 Google 在 1.3.0 中再次打破这一点。我会在那里修好它……再见,太空牛仔!
    APPCOMPAT 1.1.0 的旧答案和解决方案:
    基本上,在后台发生的事情是,当您在 attachBaseContext 中正确设置了配置时, AppCompatDelegateImpl然后将配置覆盖为没有语言环境的全新配置:
     final Configuration conf = new Configuration();
     conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
    
     try {
         ...
         ((android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
         handled = true;
     } catch (IllegalStateException e) {
         ...
     }
    
    In an unreleased commit by Chris Banes这实际上是固定的:新配置是基本上下文配置的深拷贝。
    final Configuration conf = new Configuration(baseConfiguration);
    conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
    try {
        ...
        ((android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
        handled = true;
    } catch (IllegalStateException e) {
        ...
    }
    
    在此版本发布之前,可以手动执行完全相同的操作。要继续使用 1.1.0 版,请将其添加到您的 attachBaseContext 下方。 :
    Kotlin 解决方案
    override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
        if (overrideConfiguration != null) {
            val uiMode = overrideConfiguration.uiMode
            overrideConfiguration.setTo(baseContext.resources.configuration)
            overrideConfiguration.uiMode = uiMode
        }
        super.applyOverrideConfiguration(overrideConfiguration)
    }
    
    Java解决方案
    @Override
    public void applyOverrideConfiguration(Configuration overrideConfiguration) {
        if (overrideConfiguration != null) {
            int uiMode = overrideConfiguration.uiMode;
            overrideConfiguration.setTo(getBaseContext().getResources().getConfiguration());
            overrideConfiguration.uiMode = uiMode;
        }
        super.applyOverrideConfiguration(overrideConfiguration);
    }
    
    这段代码的作用与 Configuration(baseConfiguration) 完全相同。确实在引擎盖下,但是因为我们是在 AppCompatDelegate 之后做的已经设置了正确的uiMode ,我们必须确保采用被覆盖的 uiMode在我们修复它之后结束,这样我们就不会丢失暗/亮模式设置。
    请注意 如果您不指定 configChanges="uiMode",这只会自行起作用在你的 list 里面。如果你这样做了,那么还有另一个错误:内部 onConfigurationChanged newConfig.uiMode不会被 AppCompatDelegateImpl 设置的onConfigurationChanged .如果您复制所有代码 AppCompatDelegateImpl,也可以修复此问题。用于计算当前夜间模式到您的基本 Activity 代码,然后在 super.onConfigurationChanged 之前覆盖它称呼。在 Kotlin 中,它看起来像这样:
    private var activityHandlesUiMode = false
    private var activityHandlesUiModeChecked = false
    
    private val isActivityManifestHandlingUiMode: Boolean
        get() {
            if (!activityHandlesUiModeChecked) {
                val pm = packageManager ?: return false
                activityHandlesUiMode = try {
                    val info = pm.getActivityInfo(ComponentName(this, javaClass), 0)
                    info.configChanges and ActivityInfo.CONFIG_UI_MODE != 0
                } catch (e: PackageManager.NameNotFoundException) {
                    false
                }
            }
            activityHandlesUiModeChecked = true
            return activityHandlesUiMode
        }
    
    override fun onConfigurationChanged(newConfig: Configuration) {
        if (isActivityManifestHandlingUiMode) {
            val nightMode = if (delegate.localNightMode != AppCompatDelegate.MODE_NIGHT_UNSPECIFIED) 
                delegate.localNightMode
            else
                AppCompatDelegate.getDefaultNightMode()
            val configNightMode = when (nightMode) {
                AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES
                AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO
                else -> applicationContext.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
            }
            newConfig.uiMode = configNightMode or (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
        }
        super.onConfigurationChanged(newConfig)
    }
    

    关于android - 迁移到 Androidx 后更改区域设置不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55265834/

    相关文章:

    Android 背景在第一个实例中未正确应用

    android - 支持库中的 GridLayout 在 API 16-17 上无法正常工作

    android - 在 Android 深层链接的特定 url 处提供 json 文件

    android - 在 Ionic 框架中使用 Genymotion 模拟器

    Android Fragment Xml 使用样式出现 fatal error

    android - 即使 fragment 已更改, fragment 元素仍然存在

    java - 将图像放入包中

    android - 在 Android 上按下电源按钮时如何防止调用 onDestroy() 后跟 onCreate()

    android - 使用 recyclerview 选择库选择 RecyclerView 项目(只需单击一下)

    java - 用户单击通知时如何启动 Activity ?