最近公司的 App 里需要用到多語(yǔ)言切換西篓,簡(jiǎn)單來(lái)說(shuō)憋活,就是如果用戶沒(méi)有選擇語(yǔ)言選項(xiàng)時(shí),App 默認(rèn)跟隨系統(tǒng)語(yǔ)言寸爆,如果用戶在 App 內(nèi)進(jìn)行了語(yǔ)言設(shè)置盐欺,那么就使用用戶設(shè)置的語(yǔ)言冗美。當(dāng)然,你會(huì)發(fā)現(xiàn)节预,App 的語(yǔ)言切換也會(huì)導(dǎo)致加載的其它資源文件進(jìn)行切換
上述內(nèi)容大概可以分為以下幾塊:
- 獲取系統(tǒng)默認(rèn)的語(yǔ)言和地區(qū)(注意地區(qū)属韧,后面會(huì)講述這里的坑)
- 更改 App 的語(yǔ)言
Android 應(yīng)用資源國(guó)際化
在正式開(kāi)始之前,先來(lái)講解一下 Android 應(yīng)用資源國(guó)際化的知識(shí)糠赦。對(duì)于資源文件的國(guó)際化拙泽,我們一般是在 Android src/main/res/
目錄下,建立對(duì)應(yīng)語(yǔ)言文件夾泼疑,格式一般為:values-語(yǔ)言代號(hào)-地區(qū)代號(hào)
荷荤,默認(rèn)的資源是不包含語(yǔ)言代號(hào)和地區(qū)代號(hào)的。一般情況下氓辣,應(yīng)用資源是沒(méi)有做任何適配的袱蚓,所以不管如何切換語(yǔ)言和地區(qū)設(shè)置喇潘,應(yīng)用顯示的資源都不會(huì)發(fā)生任何改變。
配置選項(xiàng)包括語(yǔ)言代號(hào)和地區(qū)代號(hào)絮吵。表示中文和中國(guó)的配置選項(xiàng)是 zh-rCN(zh表示中文忱屑,CN表示中國(guó))。表示英文和美國(guó)的配置選項(xiàng)是 en-rUS(en表示英文伴嗡,US表示美國(guó))瘪校。同一語(yǔ)言代號(hào)可以有多個(gè)地區(qū)代號(hào)名段,用 r 表示區(qū)分。
常見(jiàn)的國(guó)際化資源表示形式:
中文(中國(guó)):values-zh-rCN
中文(臺(tái)灣):values-zh-rTW
中文(香港):values-zh-rHK
維吾爾文(中國(guó)):values-ug-rCN
英語(yǔ)(美國(guó)):values-en-rUS
英語(yǔ)(英國(guó)):values-en-rGB
英文(澳大利亞):values-en-rAU
英文(加拿大):values-en-rCA
英文(愛(ài)爾蘭):values-en-rIE
英文(印度):values-en-rIN
英文(新西蘭):values-en-rNZ
英文(新加坡):values-en-rSG
英文(南非):values-en-rZA
阿拉伯文(埃及):values-ar-rEG
阿拉伯文(以色列):values-ar-rIL
保加利亞文: values-bg-rBG
加泰羅尼亞文:values-ca-rES
捷克文:values-cs-rCZ
丹麥文:values-da-rDK
德文(奧地利):values-de-rAT
德文(瑞士):values-de-rCH
德文(德國(guó)):values-de-rDE
德文(列支敦士登):values-de-rLI
希臘文:values-el-rGR
西班牙文(西班牙):values-es-rES
西班牙文(美國(guó)):values-es-rUS
芬蘭文(芬蘭):values-fi-rFI
法文(比利時(shí)):values-fr-rBE
法文(加拿大):values-fr-rCA
法文(瑞士):values-fr-rCH
法文(法國(guó)):values-fr-rFR
希伯來(lái)文:values-iw-rIL
印地文:values-hi-rIN
克羅里亞文:values-hr-rHR
匈牙利文:values-hu-rHU
印度尼西亞文:values-in-rID
意大利文(瑞士):values-it-rCH
意大利文(意大利):values-it-rIT
日文:values-ja-rJP
韓文:values-ko-rKR
立陶宛文:valueslt-rLT
拉脫維亞文:values-lv-rLV
挪威博克馬爾文:values-nb-rNO
荷蘭文(比利時(shí)):values-nl-BE
荷蘭文(荷蘭):values-nl-rNL
波蘭文:values-pl-rPL
葡萄牙文(巴西):values-pt-rBR
葡萄牙文(葡萄牙):values-pt-rPT
羅馬尼亞文:values-ro-rRO
俄文:values-ru-rRU
斯洛伐克文:values-sk-rSK
斯洛文尼亞文:values-sl-rSI
塞爾維亞文:values-sr-rRS
瑞典文:values-sv-rSE
泰文:values-th-rTH
塔加洛語(yǔ):values-tl-rPH
土耳其文:values–r-rTR
烏克蘭文:values-uk-rUA
越南文:values-vi-rVN
獲取系統(tǒng)默認(rèn)的語(yǔ)言和地區(qū)
總的來(lái)說(shuō),獲取語(yǔ)言和地區(qū)有三種方法:
-
通過(guò) Java 自帶的接口來(lái)實(shí)現(xiàn)窃蹋,即:
Locale locale = Locale.getLocale(); String language = locale.getLanguage(); String country = locale.getCountry();
-
通過(guò)
Configuration
來(lái)獲取//方法1,該方法已廢棄碎乃,如果在 API >= 17 的版本上使用 方法2 Locale locale = context.getResources().getConfiguration().locale; //方法2惠奸,在 API >= 17 的版本上可以使用 Locale locale = context.getResources().getConfiguration().getLocales().get(0); String language = locale.getLanguage(); String country = locale.getCountry();
其中佛南,
context.getResources()
也可以用Resources.getSystem()
來(lái)代替嵌言,前者獲取的是應(yīng)用內(nèi)部的語(yǔ)言和地區(qū)設(shè)置摧茴,后者獲取的是系統(tǒng)的語(yǔ)言地區(qū)設(shè)置,默認(rèn)情況下娃豹,前者跟隨系統(tǒng)設(shè)置购裙。
更改 App 的語(yǔ)言設(shè)置
通過(guò)上述分析,我們已經(jīng)知道怎么獲取系統(tǒng)和應(yīng)用的語(yǔ)言地區(qū)設(shè)置了躯畴。接下來(lái)薇芝,我們來(lái)講一下如何實(shí)現(xiàn) Android App 的多語(yǔ)言切換夯到。在前面我們已經(jīng)使用到了 Configuration
,這個(gè)類保存了 Android 應(yīng)用的所有設(shè)備信息峭状,詳見(jiàn) Configuration逼争。要實(shí)現(xiàn)應(yīng)用的多語(yǔ)言切換誓焦,我們所要做的就是更新 Configuration
中關(guān)于語(yǔ)言地區(qū)的屬性着帽。
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
Configuration configuration = resources.getConfiguration();
//API >= 17 可以使用
configuration.setLocale(locale);
//該方法已經(jīng)廢棄仍翰,官方建議使用 Context.createConfigurationContext(Configuration)
resources.updateConfiguration(configuration, metrics);
資源目錄結(jié)構(gòu)大致如下:
│ └── res
│ ├── drawable
│ ├── drawable-xhdpi
│ │ └── icon_test.png
│ ├── drawable-zh-rCN-xhdpi//圖標(biāo)適配
│ │ └── icon_test.png
│ ├── layout
│ │ └── activity_main.xml
│ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── values-en-rWW
│ │ └── strings.xml
│ ├── values-ja-rJP
│ │ └── strings.xml
│ ├── values-ko-rKR
│ │ └── strings.xml
│ └── values-zh-rCN
│ └── strings.xml
重新加載資源
看到這里予借,你是不是覺(jué)得就結(jié)束了频蛔?不,當(dāng)然不是瀑粥,更新 Configuration
之后三圆,如果不重啟 Activity舟肉,應(yīng)用的資源就不會(huì)被重新加載。
Intent intent = new Intent(this, MainActivity.class);
//開(kāi)始新的activity同時(shí)移除之前所有的activity
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
持久化存儲(chǔ)設(shè)置
經(jīng)過(guò)上述步驟割按,我們已經(jīng)可以看到應(yīng)用顯示的資源發(fā)生了改變磷籍,但是當(dāng)應(yīng)用被殺掉重啟后院领,之前所有的設(shè)置都已經(jīng)失效,應(yīng)用的語(yǔ)言地區(qū)又變成了系統(tǒng)默認(rèn)的丈氓,這是因?yàn)槲覀儗?duì)應(yīng)用所做的變更只是保存在內(nèi)存中强法,當(dāng)應(yīng)用被殺掉饮怯,在內(nèi)存中的數(shù)據(jù)也隨之被釋放,再次啟動(dòng)應(yīng)用的時(shí)候库倘,應(yīng)用讀取的是系統(tǒng)的 Configuration
,語(yǔ)言地區(qū)也隨之變成系統(tǒng)默認(rèn)的杆勇。
當(dāng)應(yīng)用需要保存用戶更改的操作饱亿,就需要對(duì)用戶的選擇進(jìn)行持久化路捧,并在應(yīng)用重啟的時(shí)候传黄,從配置中讀取并應(yīng)用該配置。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//一般在 Application 的 onCreate() 方法中更新 Configuration
LanguageUtil.changeAppLanguage(this, Locale.SIMPLIFIED_CHINESE, true);
}
}
LanguageUtil.java
/**
* 更改應(yīng)用語(yǔ)言
*
* @param context
* @param locale 語(yǔ)言地區(qū)
* @param persistence 是否持久化
*/
public static void changeAppLanguage(Context context, Locale locale,
boolean persistence) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
Configuration configuration = resources.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(locale);
} else {
configuration.locale = locale;
}
resources.updateConfiguration(configuration, metrics);
if (persistence) {
saveLanguageSetting(context, locale);
}
}
private static void saveLanguageSetting(Context context, Locale locale) {
String name = context.getPackageName() + "_" + LANGUAGE;
SharedPreferences preferences =
context.getSharedPreferences(name, Context.MODE_PRIVATE);
preferences.edit().putString(LANGUAGE, locale.getLanguage()).apply();
preferences.edit().putString(COUNTRY, locale.getCountry()).apply();
}
這樣,Android 應(yīng)用內(nèi)多語(yǔ)言切換基本完工凡伊。接下來(lái)窒舟,分享一下我在多語(yǔ)言切換過(guò)程中遇到的坑。
多語(yǔ)言切換中遇到的坑
-
以靜態(tài)變量的方式银还,在 Application 初始化時(shí)初始化網(wǎng)絡(luò)請(qǐng)求錯(cuò)誤提示語(yǔ)蛹疯,然后再系統(tǒng)中切換語(yǔ)言后热监,網(wǎng)絡(luò)請(qǐng)求錯(cuò)誤提示語(yǔ)未更新。
解決辦法:使用時(shí)直接通過(guò)
getString()
方法獲取 -
App 多語(yǔ)言切換設(shè)置持久化后列吼,在應(yīng)用啟動(dòng)時(shí)寞钥, Application 的
onCreate()
中也進(jìn)行了多語(yǔ)言切換盈简。然后去系統(tǒng)設(shè)置中切換語(yǔ)言太示,App 也會(huì)隨之跟隨系統(tǒng)語(yǔ)言类缤。原因:在我們改變系統(tǒng)的語(yǔ)言時(shí)邻吭,應(yīng)用的
Configuration
也隨之跟隨系統(tǒng)改變囱晴,而不是我們啟動(dòng)應(yīng)用時(shí)的設(shè)置了解決辦法:監(jiān)聽(tīng) Activity 的生命周期,在 Activty 的
onCreate()
中判斷應(yīng)用當(dāng)前的語(yǔ)言設(shè)置是否與用戶設(shè)置值相同驮瞧,否則強(qiáng)制更新應(yīng)用語(yǔ)言設(shè)置枯芬。因?yàn)椋?dāng)系統(tǒng)切換語(yǔ)言選項(xiàng)的時(shí)候狂魔,系統(tǒng)會(huì)重啟 Activity最楷,就如前文所說(shuō)待错,我們需要重啟 Activity 才能實(shí)現(xiàn)資源的重新加載一樣。這里也有兩種方案:- 創(chuàng)建一個(gè)基類
BaseActivity
蚯撩,在其onCreate()
方法中做處理 - 使用
ActivityLifecycleCallbacks
胎挎,在其回調(diào)onActivityCreated()
中做處理
對(duì)比一下忆家,上述兩種方案,第一種只能針對(duì)繼承自
BaseActivity
的才有效揭芍,第二種則是監(jiān)聽(tīng)所以 Activity 的生命周期称杨。所以相對(duì)而言,第二種方案更好點(diǎn)悬而。//判斷是否與設(shè)定的語(yǔ)言相同. public static boolean isSameWithSetting(Context context) { Locale current = context.getResources().getConfiguration().locale; return current.equals(getAppLocale(context)); }
public class App extends Application { @Override public void onCreate() { super.onCreate(); LanguageUtil.init(this); //注冊(cè)Activity生命周期監(jiān)聽(tīng)回調(diào) registerActivityLifecycleCallbacks(callbacks); } ActivityLifecycleCallbacks callbacks = new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { //強(qiáng)制修改應(yīng)用語(yǔ)言 if (!LanguageUtil.isSameWithSetting(activity)) { LanguageUtil.changeAppLanguage(activity, LanguageUtil.getAppLocale(activity)); } } //Activity 其它生命周期的回調(diào) }; }
- 創(chuàng)建一個(gè)基類
-
對(duì)于在 AndroidManifest.xml 中配置
launchMode
為singleInstance
的 Activity笨奠,使用Intent intent = new Intent(this, MainActivity.class); //開(kāi)始新的activity同時(shí)移除之前所有的activity intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent);
資源文件不更新唤殴。
原因:
launchMode
為singleInstance
的 Activity 與當(dāng)前應(yīng)用時(shí)不在同一個(gè) Task 棧解決方法:將
launchMode
改為其它模式或者殺掉應(yīng)用重新啟動(dòng) -
資源文件夾為
values-zh-rCN
時(shí)朵逝,將應(yīng)用 Locale 設(shè)置為Locale.CHINESE
時(shí),找不到對(duì)應(yīng)的資源文件页响。原因:
values-zh-rCN
對(duì)應(yīng)的 Locale 為Locale.SIMPLIFIED_CHINESE
解決辦法:將 Locale 設(shè)置為
Locale.SIMPLIFIED_CHINESE
或者將資源文件改為values-zh
這是踩得最慘的一個(gè)坑,浪費(fèi)了大量時(shí)間栈拖,所以才會(huì)有開(kāi)頭 Android 應(yīng)用資源國(guó)際化 那么一小節(jié)插曲涩哟。 -
這是在 華為 Nexus 6P 上測(cè)出來(lái)的一個(gè)問(wèn)題,6P 上多語(yǔ)言的選項(xiàng)有點(diǎn)詭異:簡(jiǎn)體中文(中文)潜腻、簡(jiǎn)體中文(香港)器仗、繁體中文(香港)精钮、簡(jiǎn)體中文(澳門)、繁體中文(臺(tái)灣)忽你、簡(jiǎn)體中文(新加坡)臂容,其中有幾個(gè)簡(jiǎn)體中文的選項(xiàng)在以前的 Android 版本中是沒(méi)有的,而且簡(jiǎn)體中文(香港)和繁體中文(香港)的語(yǔ)言地區(qū)都是"zh-HK"糟秘,后面在調(diào)試中發(fā)現(xiàn)蚌堵,Locale 對(duì)象中發(fā)現(xiàn)了 script 屬性,簡(jiǎn)體中文對(duì)應(yīng)
Hans
督赤,繁體中文對(duì)應(yīng)Hant
泻蚊,其余語(yǔ)言默認(rèn)為空/** * 是否用中文 * * @return true是中文 */ public static boolean isChinese() { String language = Locale.getDefault().getLanguage(); boolean isZh = Locale.SIMPLIFIED_CHINESE.getLanguage().equals(language); //API 21以上性雄,在Nexus出現(xiàn)繁體中文(香港)和簡(jiǎn)體中文(香港) //通過(guò)Locale.getScript()區(qū)分 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { String script = Locale.getDefault().getScript(); //Hans表示簡(jiǎn)體中文秒旋,Hant表示繁體中文 return isZh && "Hans".equals(script); } else { String country = Locale.getDefault().getCountry(); return isZh && Locale.SIMPLIFIED_CHINESE.getCountry().equals(country); } }
代碼已上傳 Git,歡迎大家 Star 和 Fork煤蚌。