本文为原创文章,如需转载请注明出处,谢谢!
最近项目出现一个语言设置的 bug,情况是这样:在程序中,语言默认选择的是「跟随系统」(系统语言列表中「简体中文」是第一个),然后选择「英语」,之后再切换回「跟随系统」,发现语言并没切回「简体中文」,而还是英文。这篇文章就给大家分享一下我是如何解决这个 bug 的。
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 及以前的解析策略。
如表中所示,6.0 及以前的系统在解析语言资源时,如果程序中没找到匹配的语言,就会直接使用默认语值(en)。接下来看看 7.0 做了哪些优化。
我们发现,如果 fr_CH 未能匹配到资源,系统会继续查找程序中有没有 fr 的子资源,于是找到了 fr_FR,而不再使用 en 作为默认语言。接下来再看另外一种情况。
用户在系统语言列表中添加了两个语言,在 fr 依次匹配失败以后,系统会继续查找用户的第二语言 it_CH,然而还是没匹配到,但是找到了 it 的子语言 it_IT,所以选其作为默认语言以更好的满足用户的需求。
在 7.0 中,系统提供了新的 APILocaleList.getDefault()
,用于获取当前设置中的语言列表,之后我们也需要通过这个 API 来解决 bug。
bug 细节描述
首先,我先要说一下怎样用代码动态的设置当前的语言,大致分为 3 步:
- 更改 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,我的解决思路是:
- 了解最新系统的特性,和之前的最大区别是什么,可以应用的新 API 是什么
- Debug 看看核心问题究竟出现在哪个环节
- 结合新特性和新 API寻找解决方案
- 充分利用 Android 给我们提供的资源
我也是个初学者,如写的有问题,请及时联系我!感谢!
Blog地址 https://androidzzt.github.io/