Android多语言/国际化灵活系统不畏惧NEW TASK
发表于2016-08-26
我们知道,Android的国际化需要在values那里做折腾,多配置几个string文件,结合Resources和Configuration等。礼拜天闲来无事,折腾一个Demo出来。

从上图可以看出,当我们在启动页切换语言的时候,实际上是打开了一次启动页。这样是为了“让一切重新开始”。这个后面会说明缘由。
一、从values文件夹说起。
我们知道我们新建工程的时候会带有一个values文件夹,里面string.xml文件就是我们放硬编码索引的文件。
现在,假设我们在res目录下新建多三个文件夹,跟values同级。分别如下
values-zh
values-zh-rCN
values-zh-rTW
其中values保持不变,后缀的zh表示中文(en则表示英文),后缀的rCN、rTW其中‘r’是一个标记,表示后面跟着的CN、TW是国家或地区标志。(后面我们会附上一份各个国家和地区的语言信息)
所以以上三个资源文件夹表示所对应的语言环境分别为:
中文
中文-中国 (即中文简体)
中文-台湾 (即中文繁体)
这几个文件的作用以及采用策略
默认情况下,Android会根据系统的语言地区设置,自动选择对应的资源。
首先尝试语言地区全匹配,如果没有权匹配的资源包,则会尝试匹配语言,最后则会取默认的。
比如如果Android系统的语言地区是中文简体,则首先会尝试从/values-zh-rCN中获取资源,如果没有此文件夹或者文件夹中没有响应的资源,则会尝试/values-zh,都获取不到的情况下即从/values中获取。(/values是必须存在的,否则不能通过编译)
在上面的demo中,我们只分成了3种语言,分别是简体,繁体和英文。
又由于我们默认的(values)是简体中文,所以本文中之另外建立了values-zh-rTW和values-en两个文件夹,分别表示繁体中文和英文。

二、代码
怎么改变app采用的语言?
Resources resources = getContext().getResources(); DisplayMetrics dm = resources.getDisplayMetrics(); Configuration config = resources.getConfiguration(); // 应用用户选择语言 config.locale = Locale.ENGLISH; resources.updateConfiguration(config, dm); |
本文用了 Locale 中的预设值
Locale.ENGLISH
Locale.TRADITIONAL_CHINESE
Locale.SIMPLIFIED_CHINESE,
分别表示英语,繁体中文,简体中文。
注:跟随系统设置是 Locale.getDefault()
我们上面的gif中,切换语言之后回到了启动页,这样是为了让所有页面都切换到正确语言,比如现在有ABC三个页面,我们依次顺序打开,如果只在C执行改变语言的代码,是没有办法让整个app的语言都进行切换的,所以我们才要回到“最开始的地方”,保证所有页面的遇到都得到切换。至于这点微博也是如此操作,至于微信,他是回到主页,个人估计是在Splash的一个遥望星球的页面做了处理。
接下来看一下我们启动页MainActivity的代码
public class MainActivity extends Activity { private TextView mTvChooseLan; private TextView mTvSetting; private TextView mTvOther; private TextView mTvNesTask; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTvChooseLan = (TextView) findViewById(R.id.mTvChooseLan); mTvSetting = (TextView) findViewById(R.id.mTvSetting); mTvOther = (TextView) findViewById(R.id.mTvOther); mTvNesTask = (TextView) findViewById(R.id.mTvNesTask); mTvOther.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { startActivity( new Intent(MainActivity. this ,OtherActivity. class )); } }); mTvSetting.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { startActivity( new Intent(MainActivity. this ,SettingActivity. class )); } }); mTvNesTask.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { startActivity( new Intent(MainActivity. this ,SingleInstanceActivity. class )); } }); mTvChooseLan.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { initPopupWindow(mTvChooseLan); } }); } // 选择语言的pop PopupWindow popupWindow; public void initPopupWindow(View view) { if (popupWindow == null ) { View popupView = LayoutInflater.from(MainActivity. this ).inflate(R.layout.item_swich_language, null ); popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true ); initClick(popupView); } popupWindow.setOutsideTouchable( true ); popupWindow.setBackgroundDrawable( new BitmapDrawable()); popupWindow.setFocusable( true ); popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod); popupWindow.showAtLocation(view, Gravity.CENTER, 0 , 0 ); } private void initClick(View popupView) { TextView mTvSimpleChinese = (TextView) popupView.findViewById(R.id.mTvSimpleChinese); TextView mTvTwChinese = (TextView) popupView.findViewById(R.id.mTvTwChinese); TextView mTvEnglish = (TextView) popupView.findViewById(R.id.mTvEnglish); mTvSimpleChinese.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { switchLanguage( "zh_simple" ); popupWindow.dismiss(); } }); mTvTwChinese.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { switchLanguage( "zh_tw" ); popupWindow.dismiss(); } }); mTvEnglish.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { switchLanguage( "en" ); popupWindow.dismiss(); } }); } /** * 切换语言,这里参数之所以传String不传Local是为了Sp方便存值 * @param language */ private void switchLanguage(String language) { //设置应用语言类型 Resources resources = getResources(); Configuration config = resources.getConfiguration(); DisplayMetrics dm = resources.getDisplayMetrics(); if (language.equals( "zh_simple" )) { config.locale = Locale.SIMPLIFIED_CHINESE; } else if (language.equals( "zh_tw" )){ config.locale = Locale.TRADITIONAL_CHINESE; } else if (language.equals( "en" )){ config.locale = Locale.ENGLISH; } else { config.locale = Locale.getDefault(); } resources.updateConfiguration(config, dm); //保存设置语言的类型 CacheUtils.setString(MainActivity. this , AppConstant.LANGUAGE_RUN, language); //更新语言后,destroy当前页面,重新绘制 finish(); Intent it = new Intent(MainActivity. this , MainActivity. class ); //清空任务栈确保当前打开activit为前台任务栈栈顶 it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(it); } } |
其中核心代码无非就是这段
private void switchLanguage(String language) { //设置应用语言类型 Resources resources = getResources(); Configuration config = resources.getConfiguration(); DisplayMetrics dm = resources.getDisplayMetrics(); if (language.equals( "zh_simple" )) { config.locale = Locale.SIMPLIFIED_CHINESE; } else if (language.equals( "zh_tw" )){ config.locale = Locale.TRADITIONAL_CHINESE; } else if (language.equals( "en" )){ config.locale = Locale.ENGLISH; } else { config.locale = Locale.getDefault(); } resources.updateConfiguration(config, dm); //保存设置语言的类型 CacheUtils.setString(MainActivity. this , AppConstant.LANGUAGE_RUN, language); //更新语言后,destroy当前页面,重新绘制 finish(); Intent it = new Intent(MainActivity. this , MainActivity. class ); //清空任务栈确保当前打开activit为前台任务栈栈顶 it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(it); } |
点击Pop之后之后,根据传入值改变语言,并且我们将选择的语言存进了Sp,可以会根据需求在合适的地方取出并且设语言。最后重新打开启动页MainActivity,并且清空所有之前打开的页面。(加一层保证)
通常来说如果是在登录页做改变语言的话这样就可以了。
但是如果想在设置页面改变语言,我们就需要考虑多一个问题:
1、我们普通的Activity都是存在于同一个任务栈的,语言改变都是改变同一个任务栈Activity。
2、对于singleInstance这一启动模式自己有独立的任务栈。
假设我们现在A是启动页,B是singleInstance启动模式,C是设置页。这时候我们A打开B,B在再开C,这时候BA在任务栈S1,B为S1的栈顶,C在任务栈S2里面,这个程序有S1和S2两个任务栈,并且S2为前台任务栈。
TaskRecord{3fb53a50 # 52 A=com.amqr.mutilanguage U= 0 sz= 2 } Run # 5 : ActivityRecord{151fbb29 u0 com.amqr.mutilanguage/.SettingActivity t52} TaskRecord{116c20b0 # 53 A=com.amqr.mutilanguage U= 0 sz= 1 } Run # 4 : ActivityRecord{108ffc05 u0 com.amqr.mutilanguage/.SingleInstanceActivity t53} TaskRecord{3fb53a50 # 52 A=com.amqr.mutilanguage U= 0 sz= 2 } Run # 3 : ActivityRecord{375ad5f2 u0 com.amqr.mutilanguage/.MainActivity t52} |
这时候在C页面改变系统比语言,把中文切换英文,系统记住这个时候语言要改变,按照我们前面做的自然是S1成为栈顶,至此S1的所有Activity都会变成我们指定的语言。这点没问题,但是S2可不这么想,他是一个独立的任务栈啊。也就是说,你A开启B的时候,人家B记住的状态时中文,当你在C切为A指定切换成英文的时候,没错你的任务栈S1可以可以全部变成英文,但是人家S2记住的状态是S2你改变不了。这时会你从A打开B,会发B还是中文状态。
所以当我们在标记为singleInstance的之后打开的页面改变语言的时候,我们可以可以直接杀掉当前 App 的进程,保证是“整个”程序重启。这样那些“S2”也会被干掉,整个程序的语言就一致了。
杀死当前程序进程,可以采用两行代码
android.os.Process.killProcess(android.os.Process.myPid()); System.exit( 0 ); |
根据上面的分析的,我们来看看我们的SettingActivity的代码:
public class SettingActivity extends Activity{ private TextView mTvOtherPageSwitch; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_setting); mTvOtherPageSwitch = (TextView) findViewById(R.id.mTvOtherPageSwitch); mTvOtherPageSwitch.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { initPopupWindow(mTvOtherPageSwitch); } }); } // 选择语言的pop PopupWindow popupWindow; public void initPopupWindow(View view) { if (popupWindow == null ) { View popupView = LayoutInflater.from(SettingActivity. this ).inflate(R.layout.item_swich_language, null ); popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true ); initClick(popupView); } popupWindow.setOutsideTouchable( true ); popupWindow.setBackgroundDrawable( new BitmapDrawable()); popupWindow.setFocusable( true ); popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod); popupWindow.showAtLocation(view, Gravity.CENTER, 0 , 0 ); } private void initClick(View popupView) { TextView mTvSimpleChinese = (TextView) popupView.findViewById(R.id.mTvSimpleChinese); TextView mTvTwChinese = (TextView) popupView.findViewById(R.id.mTvTwChinese); TextView mTvEnglish = (TextView) popupView.findViewById(R.id.mTvEnglish); mTvSimpleChinese.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { switchLanguage( "zh_simple" ); popupWindow.dismiss(); } }); mTvTwChinese.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { switchLanguage( "zh_tw" ); popupWindow.dismiss(); } }); mTvEnglish.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { switchLanguage( "en" ); popupWindow.dismiss(); } }); } private void switchLanguage(String language) { //设置应用语言类型 Resources resources = getResources(); Configuration config = resources.getConfiguration(); DisplayMetrics dm = resources.getDisplayMetrics(); if (language.equals( "zh_simple" )) { config.locale = Locale.SIMPLIFIED_CHINESE; } else if (language.equals( "zh_tw" )){ config.locale = Locale.TRADITIONAL_CHINESE; } else if (language.equals( "en" )){ config.locale = Locale.ENGLISH; } else { config.locale = Locale.getDefault(); } resources.updateConfiguration(config, dm); //保存设置语言的类型 CacheUtils.setString(SettingActivity. this , AppConstant.LANGUAGE_RUN, language); //更新语言后,destroy当前页面,重新绘制 finish(); Intent it = new Intent(SettingActivity. this , MainActivity. class ); startActivity(it); android.os.Process.killProcess(android.os.Process.myPid()); System.exit( 0 ); } } |
主要的区别就是下面两行代码
//更新语言后,destroy当前页面,重新绘制 finish(); Intent it = new Intent(SettingActivity. this , MainActivity. class ); startActivity(it); android.os.Process.killProcess(android.os.Process.myPid()); System.exit( 0 ); |
这样关于启动页和app设置页面改变语言的问题应该算大致解决了。
但是app替换安装或者改变手机系统语言的还是存在一些小问题的,语言不跟着换。
三、其他问题的解决
由于前面我们改变的语言的时候已经选择结果缓存到了sp。现在这个sp终于有用了。
关于替换安装的app的问题解决
弄一个Application,
public class MyApplication extends Application{ @Override public void onCreate() { super .onCreate(); String lan = CacheUtils.getString(getApplicationContext(), AppConstant.LANGUAGE_RUN, "def" ); System.out.println( "======之前选择的语言: " +lan); getLanguage(lan); } private void getLanguage(String lan){ Resources resources = getResources(); Configuration config = resources.getConfiguration(); DisplayMetrics dm = resources.getDisplayMetrics(); if (lan.equals( "zh_simple" )) { config.locale = Locale.SIMPLIFIED_CHINESE; } else if (lan.equals( "zh_tw" )){ config.locale = Locale.TRADITIONAL_CHINESE; } else if (lan.equals( "en" )){ config.locale = Locale.ENGLISH; } else { config.locale = Locale.getDefault(); } resources.updateConfiguration(config, dm); } } |
关于用户切换系统语言
此法偏流氓
public class BaseActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); System.out.println( "=============== BaseActivity onConfigurationChanged 执行" ); String lan = CacheUtils.getString(getApplicationContext(), AppConstant.LANGUAGE_RUN, "def" ); System.out.println( "======之前选择的语言: " + lan); getLanguage(lan); } private void getLanguage(String lan) { Resources resources = getResources(); Configuration config = resources.getConfiguration(); DisplayMetrics dm = resources.getDisplayMetrics(); if (lan.equals( "zh_simple" )) { config.locale = Locale.SIMPLIFIED_CHINESE; } else if (lan.equals( "zh_tw" )) { config.locale = Locale.TRADITIONAL_CHINESE; } else if (lan.equals( "en" )) { config.locale = Locale.ENGLISH; } else { config.locale = Locale.getDefault(); } resources.updateConfiguration(config, dm); } } |
四、values对应的国家和地区
输入简体字,点下面繁体字按钮进行在线转换在res目录下建立不同名称的values文件来调用不同的语言包
Values文件汇总如下:
中文(中国):values-zh-rCN中文(台湾):values-zh-rTW
中文(香港):values-zh-rHK
英语(美国):values-en-rUS
英语(英国):values-en-rGB
英文(澳大利亚):values-en-rAU
英文(加拿大):values-en-rCA
英文(爱尔兰):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
德文(德国):values-de-rDE
德文(列支敦士登):values-de-rLI
希腊文:values-el-rGR
西班牙文(西班牙):values-es-rES
西班牙文(美国):values-es-rUS
芬兰文(芬兰):values-fi-rFI
法文(比利时):values-fr-rBE
法文(加拿大):values-fr-rCA
法文(瑞士):values-fr-rCH
法文(法国):values-fr-rFR
希伯来文: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
荷兰文(比利时):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
塔加洛语:values-tl-rPH
土耳其文:values–r-rTR
乌克兰文:values-uk-rUA
越南文:values-vi-rVN
五、参考学习
Android App 多语言切换
Android的多语言实现
Android多国语言文件夹命名方式
六、下载链接
demo