Android多语言/国际化灵活系统不畏惧NEW TASK

本文出自 “阿敏其人” 简书博客,转载或引用请注明出处。

前言:

我们知道,Android的国际化需要在values那里做折腾,多配置几个string文件,结合Resources和Configuration等。礼拜天闲来无事,折腾一个Demo出来。

先上图,再扯淡

多语言演示demo.gif

从上图可以看出,当我们在启动页切换语言的时候,实际上是打开了一次启动页。这样是为了“让一切重新开始”。这个后面会说明缘由。

一、从values文件夹说起。

我们知道我们新建工程的时候会带有一个values文件夹,里面string.xml文件就是我们放硬编码索引的文件。

现在,假设我们在res目录下新建多三个文件夹,跟values同级。分别如下

  • values-zh

  • values-zh-rCN

  • values-zh-rTW

其中values保持不变,后缀的zh表示中文(en则表示英文),后缀的rCN、rTW其中‘r’是一个标记,表示后面跟着的CN、TW是国家或地区标志。(后面我们会附上一份各个国家和地区的语言信息)

所以以上三个资源文件夹表示所对应的语言环境分别为:

  • 中文

  • 中文-中国 (即中文简体)

  • 中文-台湾 (即中文繁体)

一、1 这几个文件的作用以及采用策略

默认情况下,Android会根据系统的语言地区设置,自动选择对应的资源。

首先尝试语言地区全匹配,如果没有权匹配的资源包,则会尝试匹配语言,最后则会取默认的。

比如如果Android系统的语言地区是中文简体,则首先会尝试从/values-zh-rCN中获取资源,如果没有此文件夹或者文件夹中没有响应的资源,则会尝试/values-zh,都获取不到的情况下即从/values中获取。(/values是必须存在的,否则不能通过编译)

在上面的demo中,我们只分成了3种语言,分别是简体,繁体和英文。

又由于我们默认的(values)是简体中文,所以本文中之另外建立了values-zh-rTW和values-en两个文件夹,分别表示繁体中文和英文。

Paste_Image.png

二、代码

怎么改变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终于有用了。

三、1、关于替换安装的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);

}

}

三、2、关于用户切换系统语言

此法偏流氓


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

本篇完。

推荐阅读更多精彩内容

  • 让程序支持语言国际化,这是个基础知识点,做android开发的或多或少都知道一些,只是可能大家在做工作的时候,可能...
    SupLuo阅读 1,958评论 0 51
  • 前言 android的国际化很简单,大家都知道创建对应国家编码的values文件夹就好啦。可是很多人不知道如何手动...
    再见信仰阅读 1,613评论 0 4
  • 在res目錄下建立不同名稱的values文件來調用不同的語言包Values文件匯總如下:中文(中國):values...
    江湖十年夜雨阅读 1,428评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 129,011评论 19 550
  • 我是日记星球266号星宝宝,我正在参加日记星球第六期蜕变之旅,这是我的第008篇原创日记。 坐标北京,天气晴,星期...
    纳兰珂珂阅读 60评论 0 3