以编程方式更改语言 (Android N 7.0 - API 24)

Change language programmatically (Android N 7.0 - API 24)

我正在使用以下代码在我的应用程序中设置特定语言。语言保存在应用程序内的 SharedPreferences 中。 它在 API 23 级之前都能完美运行。 使用 Android N SharedPreferences 也能正常运行,returns 正确的语言代码-string,但它不会更改语言环境(设置 phone 的默认语言)。有什么问题吗?

更新 1: 当我在 res.updateConfiguration(config, dm) 之后立即使用 Log.v("MyLog", config.locale.toString()); 时 returns 正确的语言环境,但应用程序的语言没有改变.

更新 2: 我还提到,如果我更改语言环境然后重新启动 activity(使用新意图并完成旧意图),它会改变语言正确,它甚至在旋转后显示正确的语言。但是当我关闭应用程序并再次打开它时,我会看到默认语言。很奇怪。

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: 答案也在这里:

创建一个新的 class extends 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));
}

完成activity并重新开始,新的语言环境将生效。

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

此方法适用于所有 api 级别的设备,确保以编程方式更改语言时重新创建 activity。

1.Use attachBaseContext 的基础 Activity 以设置语言环境并为所有活动扩展此 activity

open class BaseAppCompactActivity() : AppCompatActivity() {
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(LocaleHelper.onAttach(newBase))

    }

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

    }
}

2.Use 应用程序 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.Use 用于处理语言更改的 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
}
}

当我开始以 SDK 29 为目标时,我遇到了这个问题。我创建的 LocaleHelper 适用于从我的最小 SDK(21) 到 29 的每个版本,但特别是在 Android N 上它没有使用工作。因此,在搜索了许多堆栈溢出答案并访问 https://issuetracker.google.com/issues/37123942 之后,我设法 find/modify 一个解决方案,该解决方案现在适用于所有 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
    }
}

然后,在基础 activity 中,覆盖 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)
    }

但我发现这曾经适用于牛轧糖及更高版本,而不适用于棉花糖和棒棒糖。所以,我尝试了这个:

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

在 base activity 的 oncreate 中我调用了这个方法。它开始工作了。但在某些情况下,如果我们从最近的任务中终止应用程序然后启动它,第一个屏幕不会本地化。因此,为了解决这个问题,我在应用 class 中创建了一个静态临时变量,用于保存初始应用实例的值。

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)

现在我在 redmi 4a (Android 7) 上进行了测试,之前无法在其上运行,sdk 21、23、27、29 模拟器以及 redmi note 5 pro 和像素设备。在所有这些中,应用内语言选择都正常工作。

抱歉这么长 post,但请提出优化方法! 谢谢!

编辑 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!

我遇到了同样的问题,但是 Android 5、6 和 7。经过大量搜索,解决方案在这个 post 中:(请参阅评论Ehsan Shadi 而不是“解决方案”)。

首先,您必须将您的库 appcompat 更新为 1.2.0(修复区域设置,请参阅此处:https://developer.android.com/jetpack/androidx/releases/appcompat#1.2.0)并且您必须覆盖基础 activity 中的 applyOverrideConfiguration 函数。

我已经在所有 API(19 到 30 - Android 4.4 到 11)上测试了这个解决方案并且它工作得很好:)