Android 8.0 App内切换语言不生效的问题记录

国内大部分简单应该都只做了中文简体版,但是有部分项目需要面向国际化,甚至可能就是主打国外市场。因此我们有时候会遇到需要APP内做多语言切换的功能需求。
如何做多语言切换,网上资料还是很多的,本文也不是记录如何做这个功能,就不详细描述了。简单说一下我公司的项目的做法。

1.首次安装进入APP,读取手机的语言设置。如果该语言APP内做了适配则APP语言也使用该种,否则默认使用英文。系统会记住这个值,后续就算手机语言更换,APP也还是会使用第一次打开时记住的语言。
2.用户可以在APP内的设置内切换可选的语言,后续系统就记住这个值,每次都用该语言,除非用户再次在APP内更换语言。
3.技术角度分析,点击更换其他语言的时候,保存语言设置值在本地,发送广播通知语言更换。刚开始是这样处理的,每个Activity均继承自BaseActivity,BaseActivity中进行了广播动态注册(关于广播的静态注册与动态注册也不啰嗦了)。更该语言之后调用方法更改APP的更改语言配置的方法,该方法是全局变化,然后再发送广告,每个Activity都动态注册了广告,监听到语言更换的广播之后,调用recreate方法,则每个存活的Activity都会重启一下更换成了设置的语言。

下面的代码片段是之前更新语言设置的部分代码

Resources resources = mContext.getApplicationContext().getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
Configuration config = resources.getConfiguration();

config.locale = getSetLocale(); // getSetLocale方法是获取新设置的语言
resources.updateConfiguration(config, dm);

提个题外话,其实有些APP比如微信,更换语言的时候就是会重新回到主界面去,而我这里采用的停留在原界面把所有界面都recreate的方式也是看个人喜好和需求的,当前的设置的原界面会闪一下来切换语言,如果接受不了的不妨按照微信APP的方式。然后关于如何处理更换语言也是方式很多种,一般都会写个工具类啥的,然后要不要使用广播来通知整个APP语言切换也都是大家自己决定。

一、8.0安卓系统广播
那么问题是什么呢,之前一直使用这种方式没出什么问题。但是就在最近有香港的同事在测试APP的时候发现他的手机无法语言切换,而我们都OK。询问得知他的手机系统是8.0的系统。使用8.0模拟器测试后也发现了这个问题,调试发现广播接收者根本没收到广告,很奇怪,然后就来搜索安卓8.0系统广播问题,然后知道8.0系统针对广播做了一些处理,其实在7.0就开始了,只不过8.0系统彻底大改动导致之前的广播方法行不通。
网上给了两种解决方案,使用动态广播注册或者针对静态注册添加指定广播出去的包名、接受者的全路径类名,但是不知道是哪里出了问题,我本来就是动态注册来着… 有考虑过是否是因为我每个都是继承基类,然后每个基类都动态注册了的原因。但是因为我也认识到这种动态注册方式的不可取,所以干脆尝试了下静态注册,然后成功监听到广播了。

所以我就把每个界面都动态注册语言更换广播改成了静态注册广播,我们的工程内有记录Activity列表,当初是用于更好的处理退出整个APP,在这里的话就可以用来静态注册的广播收到广播后,调用整个Activity列表每个类都执行recreate方法,虽然差不多,但是更好统一方便管理了,而且更换之后也能在8.0系统上收到广播了。

之前发送广播的代码,很简单,在8.0之前的系统中运行没毛病。

Intent intent = new Intent();
intent.setAction("XXX");
mContext.sendBroaast(intent);

静态注册广播,解决8.0系统广播问题的代码,指定了广播发给那个包名以及哪个广播接收者类的全名。

Intent intent = new Intent(mContext, XXXBroadcastReceiver.class);
intent.setPackage("com.XXX.app");
intent.setComponent(new ComponentName("com.XXX.app", "com.XXX.XXXBroadcastReceiver"));
intent.setAction("XXX");
mContext.sendBroadcast(intent);

如果不是使用广播形式通知更该语言的,这些东西就没多大关系了,但是如果你的APP内也用到了广播,还没做8.0适配的话,还是赶紧拿个8.0手机测试下,有问题的话赶紧做适配处理吧~

二、8.0安卓系统APP内语言更改
广播能监听到了,也调用了recreate方法了,但是,语言确实还是没有变化。这就很奇怪了,赶紧继续搜索安卓8.0系统语言切换问题,居然针对语言切换的改动也那么大!

1、 首先,之前的语言更改代码需要做特殊处理了。针对高版本系统,调用了setLocale的方法。另外此处的Context建议使用ApplicationContext。但是网上有些8.0语言切换的方法的代码就是需要这里的Context使用当前界面的上下文,这种方式很麻烦,每个Activity都需要这样处理,但是他们的那种方法又必须Context使用该界面的上下文才生效,所以这种方法是不可取的。至于这里使用了ApplicationContext后续如何具体生效到每个界面,后面会说。

Resources resources = mContext.getApplicationContext().getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
Configuration config = resources.getConfiguration();

Locale locale = getSetLocale();// getSetLocale方法是获取新设置的语言
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    config.setLocale(locale);
} else {
    config.locale = locale;
}
resources.updateConfiguration(config, dm);

2、 其实做了多语言切换的APP都会在Application类的onCreate方法执行读取该APP设置的语言进行设置更换的方法,但是我们现在还需要在Application类加这段代码。

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    XXXX(); // 调用读取语言设置且更换APP语言的方法
}

3、语言切换的代码,在高版本中废弃了updateConfiguration方法,替代方法为createConfigurationContext。该方法是返回一个Context,也就是语言需要植入到Context中,每个Context都植入一遍。当然我们没那么傻,我们有基类,所以在BaseActivity中加入了如下代码。

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(XXXUtils.attachBaseContext(base));
}

XXXUtils代码中的attachBaseContext方法做了处理。

public Context attachBaseContext(Context context) {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // 8.0需要使用createConfigurationContext处理
       return updateResources(context);
   } else {
       return context;
   }
}

@TargetApi(Build.VERSION_CODES.N)
private Context updateResources(Context context) {
    Resources resources = context.getResources();
    Locale locale = getSetLocale();// getSetLocale方法是获取新设置的语言

    Configuration configuration = resources.getConfiguration();
    configuration.setLocale(locale);
    configuration.setLocales(new LocaleList(locale));
    return context.createConfigurationContext(configuration);
}

至此,再去8.0模拟器中测试,可以正常更换语言了。
该文章作为自己的一次适配简单记录,也提醒还没做适配的安卓开发者需要做下适配了。下面给出我参考的文章和Github的Demo,有需要的可以去查看,Demo中有写好工具类等大家可以拿来使用或者和我一样根据自己的情况参考代码自己做处理。
有错误之处大家可以指出或者交流,有疑问的也可以留言。

参考文章: https://yanlu.me/android-7-0-app-language-switch/
参考Demo:https://github.com/captain-miao/MultiLanguagesSwitch

20190219更新
如果在app中需要知道系统的语言设置,在某些地方根据系统语言做某些处理或者语言设置列表需要有一个跟随系统的选项的话,实践可行的一个解决方案是:

之前的设置语言代码,都是保存设置的语言在手机本地,在application启动时读取保存的语言设置进行设置,一旦手动更改设置了,后续再去获取语言都是获取到设置了之后的,所以需要在启动程序application初始化中,先获取系统语言保存后再把app设置为显示其他语言。后续便可以语言设置更改会跟随系统。
在application的onConfigurationChanged中,还能监听到app使用过程中系统语言设置的更改,这样更能保证保存的是实时的正确的系统语言。(微信的跟随系统,在微信使用过程中更改了系统语言后跟随系统语言设置还能读取设置的之前的系统语言,可能就是没做这个监听重置,当然,这些都是小问题而且一般用户也不会这样操作)
经测试发现系统语言更改了,application不是重走onCreate方法但是onConfigurationChanged会触发,界面也会重新onCreate。

onConfigurationChanged在横竖屏切换时也会触发,另一种方式是监听广播,系统语言更改时会触发广播ACTION_LOCALE_CHANGED,未测试,但是应该是比onConfigurationChanged方法更合适的。

你可能感兴趣的:(Android,Android笔记)