Android多语言切换实现--Java实现

Author Avatar
Reborn 3月 23, 2019
  • 在其它设备中阅读本文章

前言

最近在想宝可梦的那样记账要加什么功能好呢?突然想到,不如实现多语言手动切换(国际化),方便那些为了开Google Assistant而把语言设置成英文的朋友可以在该App使用中文界面。

需求

  • 可以随着系统切换语言而切换语言(即跟随系统),不支持的语言显示默认(默认为英文)
  • 用户可以自由选择自己想用的语言,且不会随着系统切换语言或者应用重启而还原

虽然看着简单,但是实现起来还是遇到了点问题

原理

参照 Android Developer官网 上的描述,Configuration 包含了设备的所有的配置信息,这些配置信息会影响应用获取的资源。例如string资源,就是在不同的res/value-xx下放置不同语言的strings.xml实现字符的本地化,然后根据Configurationlocale属性来判断该取哪种语言的string资源,而这个value-xx目录的选择是根据locale这项的值来决定的。

如zh中文,就会选择value-zh目录,如果没有匹配到(即res中没有value-zh目录)就使用默认的value目录中的字符资源。

那么我们只需要想办法改变并重新加载Configuration即可!!!

注:value-envalue-zh-rCNvalues-zh-rTW分别表示英文简体中文繁体中文三种语言

初次尝试

1. 添加多语言文件

在不同的value文件夹下(例如valuevalue-envalue-zh-rCNvalues-zh-rTW文件夹)添加不同语言的string.xml文件,我们的项目添加了英文、简体中文、繁体中文三种语言。

翻译之类的就自行翻译啦~繁体可以找个简繁转换网站。

2. 更新 Configuration 中的 locale 属性来实现app语言的切换

主要代码如下:

Resources resources = getContext().getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
Configuration config = resources.getConfiguration();
// 应用用户选择语言,如:
// config.locale = Locale.ENGLISH;
resources.updateConfiguration(config, dm);

我们用了Locale中的预设值Locale.ENGLISHLocale.TRADITIONAL_CHINESELocale.SIMPLIFIED_CHINESE,如果你需要设置的语言没有预设值,你可以自己新建一个Locale对象,具体自行 Google 吧。

注:跟随系统设置是Locale.getDefault()

3. 重启 HomeActivity

我们的 App 有个启动页WelcomeActivity,类似微信那个小人启动页,如果从欢迎页重启,并不是一个好的体验,应该和微信的语言设置一样,直接回到HomeActivity,而不是从 WelcomeActivity重新打开。实现其实也很简单,代码如下:

Intent intent = new Intent(this, HomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
getActivity().startActivity(intent);
// 杀掉进程,如果是跨进程则杀掉当前进程
//  android.os.Process.killProcess(android.os.Process.myPid());
//  System.exit(0);

4. 持久化存储语言设置

按照上述三个步骤,你应该已经可以看到了改变语言的效果了,但是当你杀掉应用,重新打开,发现设置又失效了。这是因为应用重启后会读取设备默认的Configuration信息,其中和语言相关的locale属性也会变成默认值,也就是你在系统设置中选择的语言。

当你的应用需要由用户单独设置语言,而不是仅仅跟随系统语言,你就需要持久化存储用户的设置信息了。你可以选择数据库、或SharedPreferences来存储设置信息,存一个Type的int值即可。

5. 根据本地缓存的type获取对应的locale

其中7.0以上的系统需要另做处理(在后面会讲处理兼容性问题),具体代码如下:

Locale locale;
// 应用用户选择语言
switch (type) {
    case 0:
        locale = I18NUtils.getSystemLocale(); // getSystemLocale()是一个自定义方法,用于获取系统语言
        break;
    case 1: ...;break;
    ...
    default:
        locale = enLocale; // enLocale 是一个静态 Locale 变量,用于默认为未提供的语言显示英文语言
        break;
}

6. 在AppApplication中初始化时设置本地语言

用于每次启动APP后切换到本地缓存的语言

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        ...

        Resources resources = App.getContext().getResources();
        DisplayMetrics dm = resources.getDisplayMetrics();
        Configuration config = resources.getConfiguration();
        // 设置本地化语言
        config.locale = getSetLocaleInSP();
        resources.updateConfiguration(config, dm);

    }

    // 得到设置的语言信息
    private static Locale getSetLocaleInSP() {
        // 读取储存的语言设置信息(结合第五步)
        ...

}

7. 在BaseActivity的OnCreate()方法中设置语言

用于处理每次切换系统语言后app语言会跟随系统变化的问题。

在这一步之前会遇到一个问题:当从应用中切出去,改变了系统语言的设置,当再切应用的时候,我发现语言也会变成系统语言(而我并没在应用内设置跟随系统)。

引用自@写代码的猴子的文章
简单来说,上一步中,我们在 App 启动时,读取了用户的设置信息,并应用到 Configurationlocale属性上,然后通过resources.updateConfiguration(config, dm)改变了应用的配置信息(Configuration)并生效,保证我们的应用读取的string资源都是用户设置语言对应的资源。在我们改变系统的语言之后,再回到我们的应用中,此时的Configurationlocale属性就会发生变化了,不再是我们刚才自己的在应用启动时设置的了,而是变成了系统的设置了。

那么解决办法也很简单,我们都知道 activity 有生命周期,在切回我们的应用时,在显示的 activity 的生命周期中做一些处理就好了(有点粗暴~)

在实际开发中,我们会建立很多个 activity,而每一个 activity 都要更改语言的。这个时候我们如果一个个做处理是不是很麻烦?这个时候我们利用面向对象语言的继承特性即可,创建一个BaseActivity,在BaseActivity 中处理后其他 Activity 都去继承BaseActivity就好了。@写代码的猴子的文章中说评论中提到,在改变了系统设置之后,回到应用会重走activityonCreate(如果按照Activity的生命周期看其实应该是onResume才对),那就在OnCreate中处理一下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (!LanguageUtil.isSetValue()) {
        LanguageUtil.resetAppLanguage();
    }
    ...
}
public class LanguageUtil {

    ...

    /**
     * 是否是设置值
     *
     * @return 是否是设置值
     */
    public static boolean isSetValue() {
        Locale currentLocale = App.getContext().getResources().getConfiguration().locale;
        return currentLocale.equals(getSetLocaleInSP());
    }
}

解决7.0以上系统存在的兼容问题——跟随系统语言失效

Android 7.0 语言设置爬坑这篇文章讲的很详细,具体还可以参考官方文档官方API/)

如果不做兼容,可能会出现以下情况:在 App 中,语言默认选择的是「跟随系统」(系统语言列表中「简体中文」是第一个),然后选择「英语」,之后再切换回「跟随系统」,发现语言并没切回「简体中文」,而还是「英语」。

由于 Android7.0 以上Configuration将通过LocaleList来管理语言,并且系统切换语言后,系统默认语言可能并不在LocaleList顶部。

经过调试发现:如果在 App 中手动选择(切换)过语言则在LocaleList中系统语言是第二个,否则是第一个。

所以,获取当前系统locale,代码如下:

Locale systemLocale;
//由于API仅支持7.0,需要判断,否则程序会crash(解决7.0以上系统不能跟随系统语言问题)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    LocaleList localeList = LocaleList.getDefault();
    // 获取之前选择过语言后缓存在 SharePreferences 的语言(即当前选择的语言)
    int spType = getLanguageType(AppApplication.getAppContext());
    // 如果app已选择不跟随系统语言,则取第二个数据为系统默认语言
    if(spType != 0 && localeList.size() > 1) {
        locale = localeList.get(1);
    } else {
        locale = localeList.get(0);
    }
} else {
    locale = Locale.getDefault();
}

缺点

但是这个做出来的效果是每次启动都会重启一次Activity,强迫症的我表示根本受不了😂,而且横竖屏切换后会有语言变成系统语言的问题

参考文章