Android|Android 7.0 语言设置爬坑

本文为原创文章,如需转载请注明出处,谢谢!

最近项目出现一个语言设置的 bug,情况是这样:在程序中,语言默认选择的是「跟随系统」(系统语言列表中「简体中文」是第一个),然后选择「英语」,之后再切换回「跟随系统」,发现语言并没切回「简体中文」,而还是英文。这篇文章就给大家分享一下我是如何解决这个 bug 的。
Android 7.0 多语言特性
【Android|Android 7.0 语言设置爬坑】在开始解决 bug 之前,我们还是有必要了解 7.0 中对多语言资源的处理和之前有什么不同。在此小节中,我只阐述结论,具体的分析过程请参考这篇文章,讲的很细致。
http://blog.csdn.net/cekiasoo/article/details/53012646,
当然官方文档也要仔细阅读一下,
https://developer.android.com/guide/topics/resources/multilingual-support.html。
接下来,我直接根据官方文档中的两个表格简单解释一下 7.0 语言资源解析策略和之前系统相比有什么不同。首先我们看 6.0 及以前的解析策略。
Android|Android 7.0 语言设置爬坑
文章图片
如表中所示,6.0 及以前的系统在解析语言资源时,如果程序中没找到匹配的语言,就会直接使用默认语值(en)。接下来看看 7.0 做了哪些优化。
Android|Android 7.0 语言设置爬坑
文章图片
我们发现,如果 fr_CH 未能匹配到资源,系统会继续查找程序中有没有 fr 的子资源,于是找到了 fr_FR,而不再使用 en 作为默认语言。接下来再看另外一种情况。
Android|Android 7.0 语言设置爬坑
文章图片
用户在系统语言列表中添加了两个语言,在 fr 依次匹配失败以后,系统会继续查找用户的第二语言 it_CH,然而还是没匹配到,但是找到了 it 的子语言 it_IT,所以选其作为默认语言以更好的满足用户的需求。
在 7.0 中,系统提供了新的 APILocaleList.getDefault(),用于获取当前设置中的语言列表,之后我们也需要通过这个 API 来解决 bug。
bug 细节描述
首先,我先要说一下怎样用代码动态的设置当前的语言,大致分为 3 步:
  1. 更改 Configuration 中的 locale 属性,具体代码如下
Resources resources = getContext().getResources(); DisplayMetrics dm = resources.getDisplayMetrics(); Configuration config = resources.getConfiguration(); config.locale = Locale.ENGLISH; //设置语言 resources.updateConfiguration(config, dm);

代码中通过更改 config.locale 已经被废弃,官方推荐使用 config.setLocales(LocaleList localeList)config.getLocales() 来进行设置和获取语言。
2.持久化语言选择,可通过 SharedPreference 进行存储,然后在每次进入程序时,先取出之前的配置然后再设置。
3.重启 HomeActivity,并将设置语言的方法放在 Activity 的 onCreate()中执行,以确保每次重启时都是设置最近一次的语言配置
上面只是粗略的说了一下,如果没做过语言设置的同学可以参考这篇文章,讲的很细很好。
http://jaeger.itscoder.com/android/2016/05/14/switch-language-on-android-app.html
了解如何动态设置语言之后,我来详细描述一下 bug 的细节。 我们在设置语言时,实际就是在修改 Configuration 的一 locale 属性,但是到了 7.0 ,Configuration 将通过 LocaleList 来管理语言,bug 的产生也正源于 LocaleList。
现在假设系统语言列表中是「简体中文,英语」,程序中的语言设置选的是「跟随系统」,此时打印 LocaleList.getDefault(),得到的结果是「简体中文,英语」,没有问题。接着我在程序中将语言切换为「德语」,然后再次打印 LocaleList,得到的结果是「德语,简体中文,英语」,发现当前语言被加到了列表的第一位,此时我们再切换回「跟随系统」,即调用 Locale.getDefault() 获取系统默认语言,发现语言切换成了「德语」,并不是「简体中文」,也就是默认获取了列表中的第一个语言。
在 setLocale 后为什么被设置的语言会加到 LocaleList 的第一个,我还没探究明白,需要再仔细看看源码,如果有同学知道也可以给我解答一下!
bug 解决思路
由于每次动态设置语言之后,系统语言列表都会被改变,所以我的思路是在程序一开始的时候就把系统列表保存起来,以便之后使用,话不多说,来看一下我修改后的代码:
public class LanguageUtil {private LocaleList sLocaleList; static { //由于API仅支持7.0,需要判断,否则程序会crash if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { sLocaleList = LocaleList.getDefault(); } }public static void setLocale(Context context) { Resources res = context.getResources(); DisplayMetrics dm = res.getDisplayMetrics(); Configuration conf = res.getConfiguration(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (auto) { //选择跟随系统 conf.setLocale(sLocaleList.get(0)); } else { //设置选择的语言 conf.setLocale(getLocale()); } } else { conf.locale = getCurrentLocale(context); } res.updateConfiguration(conf, dm); }public static Locale getLocale() { Locale locale; if (auto) { locale = Locale.getDefault(); } else { //从 sharedPreference 中获取 Locale locale = getLocaleFromSP(); } return locale; }public void setSystemLocaleList(LocaleList localeList) { sLocaleList = localeList; } }

这样修改后,我们每次切换回「跟随系统」,都是直接获取程序初始化时就获取了的系统列表的第一个语言,就不会出现每次之前那种现象了。
但是,我们如果在程序中切到设置中修改了语言列表,然后再切回程序,又会出现问题了,我们在程序初始化时就获取的列表是写死的,并不会跟随系统变化而变化,所以刚才的代码还是不够健壮。如果要是能监听系统语言列表改变的话就好了,没错,解决办法就是去监听语言变化,Android 会在系统语言列表被修改时发出广播 Intent.ACTION_LOCALE_CHANGED,所以我们需要在 BaseActivity 里注册监听该广播,然后在收到语言列表被修改的广播时更新我们自己的列表,然后再设置语言,这样就可以完美解决问题了!看下代码
public class BaseActivity extends AppCompatActivity {@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.registerReceiver(localeChangedReceiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED)); }@Override protected void onDestroy() { this.unregisterReceiver(localeChangedReceiver); super.onDestroy(); }private BroadcastReceiver localeChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { LanguageUtil.setSystemLocaleList(LocaleList.getDefault()); LanguageUtil.setLocale(BaseActivity.this); } } } }; }

总结
这是第一次解决由于Android 系统升级导致的 bug,我的解决思路是:
  1. 了解最新系统的特性,和之前的最大区别是什么,可以应用的新 API 是什么
  2. Debug 看看核心问题究竟出现在哪个环节
  3. 结合新特性和新 API寻找解决方案
  4. 充分利用 Android 给我们提供的资源
我也是个初学者,如写的有问题,请及时联系我!感谢!
Blog地址 https://androidzzt.github.io/

    推荐阅读