草庐IT

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

coder 2023-11-24 原文

我有一个支持多语言的旧项目。我要升级支持库和目标平台,在迁移到 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 - 迁移到 Androidx 后更改区域设置不起作用的更多相关文章

    1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    2. ruby-on-rails - Ruby on Rails 迁移,将表更改为 MyISAM - 2

      如何正确创建Rails迁移,以便将表更改为MySQL中的MyISAM?目前是InnoDB。运行原始执行语句会更改表,但它不会更新db/schema.rb,因此当在测试环境中重新创建表时,它会返回到InnoDB并且我的全文搜索失败。我如何着手更改/添加迁移,以便将现有表修改为MyISAM并更新schema.rb,以便我的数据库和相应的测试数据库得到相应更新? 最佳答案 我没有找到执行此操作的好方法。您可以像有人建议的那样更改您的schema.rb,然后运行:rakedb:schema:load,但是,这将覆盖您的数据。我的做法是(假设

    3. ruby-openid:执行发现时未设置@socket - 2

      我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

    4. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

      我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

    5. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

      我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

    6. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

      如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

    7. ruby - Capistrano 3 在任务中更改 ssh_options - 2

      我尝试使用不同的ssh_options在同一阶段运行capistranov.3任务。我的production.rb说:set:stage,:productionset:user,'deploy'set:ssh_options,{user:'deploy'}通过此配置,capistrano与用户deploy连接,这对于其余的任务是正确的。但是我需要将它连接到服务器中配置良好的an_other_user以完成一项特定任务。然后我的食谱说:...taskswithoriginaluser...task:my_task_with_an_other_userdoset:user,'an_othe

    8. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

      我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

    9. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

      我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

    10. ruby - 更改 ActiveRecord 中对象的类 - 2

      假设我有一个FireNinja我的数据库中的对象,使用单表继承存储。后来才知道他真的是WaterNinja.将他更改为不同的子类的最干净的方法是什么?更好的是,我很想创建一个新的WaterNinja对象并替换旧的FireNinja在数据库中,保留ID。编辑我知道如何创建新的WaterNinja来self现有FireNinja的对象,我也知道我可以删除旧的并保存新的。我想做的是改变现有项目的类别。我是通过创建一个新对象并执行一些ActiveRecord魔法来替换行,还是通过对对象本身做一些疯狂的事情,或者甚至通过删除它并使用相同的ID重新插入来做到这一点,这是问题的一部分。

    随机推荐