start_time: 2024-04-20 08:41:52 +0800

Android 7.0 语言设置爬坑

96
Android_ZzT
IP属地: 上海
0.9 2017.04.16 20:58 字数 1651

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

最近项目出现一个语言设置的 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 步:

  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/

最后编辑于 :2017-12-06 19:02:45
©著作权归作者所有,转载或内容合作请联系作者
序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 157298 评论 4 赞 360
序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 66701 评论 1 赞 290
文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
8 开封第一讲书人 阅读 107078 评论 0 赞 237
文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
8 开封第一讲书人 阅读 43687 评论 0 赞 202
正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
388e473c fe2f 40e0 9301 e357ae8f1b41 茶点故事 阅读 52018 评论 3 赞 286
文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
8 开封第一讲书人 阅读 40410 评论 1 赞 211
那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 31729 评论 2 赞 310
文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
8 开封第一讲书人 阅读 30412 评论 0 赞 194
序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 34124 评论 1 赞 239
正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
388e473c fe2f 40e0 9301 e357ae8f1b41 茶点故事 阅读 30379 评论 2 赞 242
正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
388e473c fe2f 40e0 9301 e357ae8f1b41 茶点故事 阅读 31903 评论 1 赞 257
序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 28268 评论 2 赞 251
正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
388e473c fe2f 40e0 9301 e357ae8f1b41 茶点故事 阅读 32894 评论 3 赞 233
文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
8 开封第一讲书人 阅读 26014 评论 0 赞 8
文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
8 开封第一讲书人 阅读 26770 评论 0 赞 192
我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 35435 评论 2 赞 269
正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
388e473c fe2f 40e0 9301 e357ae8f1b41 茶点故事 阅读 35312 评论 2 赞 260
end_time: 2024-04-20 08:41:52 +0800 Completed in 220.919794ms