程序化地更改语言(Android N 7.0 - API 24)

6
我正在使用以下代码在我的应用程序中设置特定的语言。语言保存在应用程序内的SharedPreferences中。在API级别23之前,它可以完美地工作。在Android N中,SharedPreferences也能正常工作,它返回正确的语言代码字符串,但它不会更改区域设置(设置手机的默认语言)。可能出了什么问题? 更新1:当我在res.updateConfiguration(config, dm)之后立即使用Log.v("MyLog", config.locale.toString());时,它返回正确的区域设置,但应用程序的语言没有改变。 更新2:我还提到,如果我更改区域设置,然后重新启动活动(使用新意图并完成旧的),它会正确地更改语言,甚至在旋转后显示正确的语言。但是,当我关闭应用程序并再次打开它时,我会得到默认语言。这很奇怪。
public class ActivityMain extends AppCompatActivity {

    //...
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState);
        // Set locale
        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
        String lang = pref.getString(ActivityMain.LANGUAGE_SAVED, "no_language");
        if (!lang.equals("no_language")) {
            Resources res = context.getResources();
            Locale locale = new Locale(lang);
            Locale.setDefault(locale);
            DisplayMetrics dm = res.getDisplayMetrics();
            Configuration config = res.getConfiguration();
            if (Build.VERSION.SDK_INT >= 17) {
                config.setLocale(locale);
            } else {
                config.locale = locale;
            }
        }
        res.updateConfiguration(config, dm);

        setContentView(R.layout.activity_main);
        //...
    } 
    //... 
}

更新3:答案也在这里:https://dev59.com/C1kS5IYBdhLWcg3wiXbI#40849142


https://developer.android.com/preview/features/multilingual-support.html - CommonsWare
@CommonsWare 感谢您的回复,不幸的是这个链接并没有帮助到我。现在我也提到了,如果我改变语言环境,然后使用新的意图重新启动活动并完成旧的活动,它会正确地更改语言,甚至在旋转(重新创建活动)后显示正确的语言。但是当我关闭应用程序并再次打开时,我会得到默认语言。我不明白。这很奇怪。 - user35603
@user35603,你找到任何解决方案了吗? - Metwalli
@Jamal,很抱歉没有:( 一个可行的但十分糟糕的解决方案是在启动后立即重新启动应用程序(1次)。 - user35603
1
我向Android团队提交了一个bug,这里是他们的回答: https://code.google.com/p/android/issues/detail?id=225679 - Metwalli
1
可能是Android N更改语言编程方式的重复问题。 - Sruit A.Suk
4个回答

13

创建一个继承自ContextWrapper的新类

public class MyContextWrapper extends ContextWrapper {
    public MyContextWrapper(Context base) {
        super(base);
    }

    @TargetApi(Build.VERSION_CODES.N)
    public static ContextWrapper wrap(Context context, Locale newLocale) {
        Resources res = context.getResources();
        Configuration configuration = res.getConfiguration();

        if (VersionUtils.isAfter24()) {
            configuration.setLocale(newLocale);

            LocaleList localeList = new LocaleList(newLocale);
            LocaleList.setDefault(localeList);
            configuration.setLocales(localeList);

            context = context.createConfigurationContext(configuration);

        } else if (VersionUtils.isAfter17()) {
            configuration.setLocale(newLocale);
            context = context.createConfigurationContext(configuration);

        } else {
            configuration.locale = newLocale;
            res.updateConfiguration(configuration, res.getDisplayMetrics());
        }

        return new ContextWrapper(context);
    }
}

覆盖Activity的attachBaseContext方法

@Override
protected void attachBaseContext(Context newBase) {
    Locale languageType = LanguageUtil.getLanguageType(mContext);
    super.attachBaseContext(MyContextWrapper.wrap(newBase, languageType));
}

完成活动并重新开始,新的地区设置将生效。

示例:https://github.com/fanturbo/MultiLanguageDemo


当我更改LanguageUtil或MyContextWrapper类的语言时,我该怎么办?在闪屏界面中设置应用程序的语言。 - Mina Fawzy
@MinaFawzy 对不起,我不明白你的意思。请详细描述一下。 - turbofan
如果我不想使用attachBaseContext,是否仍然可能呢? - niczm25
6
您至少需要给予您从中复制的人以信誉...https://dev59.com/C1kS5IYBdhLWcg3wiXbI - Sruit A.Suk
@SruitA.Suk 也许你应该阅读这两个链接。https://developer.android.com/reference/android/content/res/Resources https://developer.android.com/reference/android/content/res/Configuration - turbofan

3

当我开始针对SDK 29进行开发时,我遇到了这个问题。我创建的LocaleHelper适用于从我的最小SDK(21)到29的每个版本,但具体在Android N上它不起作用。因此,在搜索了许多堆栈溢出答案并访问https://issuetracker.google.com/issues/37123942之后,我设法找到/修改了一个解决方案,现在可以在所有Android版本上工作。

所以,首先我借助https://gunhansancar.com/change-language-programmatically-in-android/获得了一个语言环境助手。然后我根据自己的需要进行了修改,如下所示:

 object LocaleHelper {
    const val TAG = "LocaleHelper"

    fun updateLocale(base: Context): Context {
        Log.e(TAG, "updateLocale: called");
        val preferenceHelper = PreferenceHelper(base)
        preferenceHelper.getStringPreference(PreferenceHelper.KEY_LANGUAGE).let {
            return if (it.isNotEmpty()) {
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
                    updateResources(base, it)
                } else {
                    updateResourcesLegacy(base, it)
                }
            } else {
                base
            }
        }
    }

    private fun updateResources(base: Context, language: String): Context{
        val loc = Locale(language)
        Locale.setDefault(loc)
        val configuration = base.resources.configuration
        configuration.setLocale(loc)
        return base.createConfigurationContext(configuration)
    }

    @Suppress("DEPRECATION")
    private fun updateResourcesLegacy(base: Context, language: String): Context{
        val locale = Locale(language)
        Locale.setDefault(locale)
        val configuration = base.resources.configuration
        configuration.locale = locale
        configuration.setLayoutDirection(locale)
        base.resources.updateConfiguration(configuration, base.resources.displayMetrics)
//                    Log.e(TAG, "updateLocale: returning for below N")
        return base
    }
}

然后,在基础活动中,重写attachBaseContext方法如下:

   /**
     * While attaching the base context, make sure to attach it using locale helper.
     * This helps in getting localized resources in every activity
     */
    override fun attachBaseContext(newBase: Context?) {
        var context = newBase
        newBase?.let {
            context = LocaleHelper.updateLocale(it)
        }
        super.attachBaseContext(context)
    }

但我发现这在Nougat及以上版本中有效,而在marshmallow和lollipop中无效。所以,我尝试了这个:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //Update the locale here before loading the layout to get localized strings.
        LocaleHelper.updateLocale(this)

在基础活动的oncreate中,我调用了这个方法。它开始工作了。但有时候,如果我们从最近任务中杀掉应用程序,然后重新启动它,第一个屏幕没有本地化。为了解决这个问题,我在应用程序类中创建了一个静态临时变量,保存初始应用程序实例的值。
companion object {
        val TAG = "App"
        var isFirstLoad = true
    }

在我的应用的第一个屏幕中,我在oncreate方法中做了这件事:
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //Need to call this so that when the application is opened for first time and we need to update the locale.
        if(App.isFirstLoad) {
            LocaleHelper.updateLocale(this)
            App.isFirstLoad = false
        }
        setContentView(R.layout.activity_dash_board)

现在我已经在红米4A(Android 7)上进行了测试,之前它无法正常工作,在SDK 21、23、27、29和红米Note 5 Pro以及Pixel设备的模拟器中都进行了测试。在所有这些设备上,应用内语言选择都能正常工作。
很抱歉发了这么长的帖子,请建议一种优化的方式!谢谢!
编辑1: 所以我遇到了一个问题,在Android 7.1(SDK 25)上无法正常工作。我向gunhan寻求帮助,然后进行了这个更改。
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
    super.applyOverrideConfiguration(LocaleHelper.applyOverrideConfiguration(baseContext, overrideConfiguration))
}

我在我的LocaleHelper中添加了这个函数:

fun applyOverrideConfiguration(base: Context, overrideConfiguration: Configuration?): Configuration? {
    if (overrideConfiguration != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        val uiMode = overrideConfiguration.uiMode
        overrideConfiguration.setTo(base.resources.configuration)
        overrideConfiguration.uiMode = uiMode
    }
    return overrideConfiguration
}

现在它终于工作了。 谢谢Gunhan!


正是我在寻找的!它运行非常好,是 Localehelper 的一个不错的补丁。 - Juan Pablo

1
我遇到了同样的问题,但是是在Android 5、6和7上。经过多次搜索,解决方案在这篇文章中:迁移到Androidx后更改区域设置无效(请参考Ehsan Shadi的评论而不是“解决方案”)。
首先,您需要将库appcompat更新到1.2.0版本(修复区域设置问题,请参见此处:https://developer.android.com/jetpack/androidx/releases/appcompat#1.2.0),并且您需要在基本活动中覆盖applyOverrideConfiguration函数。
我已在所有API上测试了这个解决方案(从19到30 - Android 4.4到11),它完美地工作了 :)

1
这种方法适用于所有 API 级别的设备,确保在程序中更改语言时重新创建活动。
1.使用基本 Activity 来附加BaseContext 以设置区域语言,然后将此 Activity 扩展到所有活动。
open class BaseAppCompactActivity() : AppCompatActivity() {
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(LocaleHelper.onAttach(newBase))

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }
}

2.使用Application的attachBaseContext和onConfigurationChanged方法设置本地化语言。

public class MyApplication extends Application {
    private static MyApplication application;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    public static MyApplication getApplication () {
        return application;
    }

    /**
     * overide to change local sothat language can be chnaged from android device  nogaut and above
     */
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(LocaleHelper.INSTANCE.onAttach(base));
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        /**
         * also handle change  language if  device language changed
         */
        super.onConfigurationChanged(newConfig);

    }
}

3. 使用Locale Helper来处理语言变化,这种方法适用于所有设备。

object LocaleHelper {
    fun onAttach(context: Context, defaultLanguage: String): Context {
        return setLocale(context, defaultLanguage)
    }

fun setLocale(context: Context, language: String): Context {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        updateResources(context, language)
    } else updateResourcesLegacy(context, language)

}


@TargetApi(Build.VERSION_CODES.N)
private fun updateResources(context: Context, language: String): Context {
    val locale = Locale(language)
    Locale.setDefault(locale)

    val configuration = context.getResources().getConfiguration()
    configuration.setLocale(locale)
    configuration.setLayoutDirection(locale)

    return context.createConfigurationContext(configuration)
}

private fun updateResourcesLegacy(context: Context, language: String): Context {
    val locale = Locale(language)
    Locale.setDefault(locale)

    val resources = context.getResources()

    val configuration = resources.getConfiguration()
    configuration.locale = locale
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        configuration.setLayoutDirection(locale)
    }
    resources.updateConfiguration(configuration, resources.getDisplayMetrics())
    return context
}
}

有关此事的信息,请参阅博客 https://proandroiddev.com/change-language-programmatically-at-runtime-on-android-5e6bc15c758 - mani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接