语言切换需求
- 应用内切换语言,支持阿拉伯语(从右到左书写,即RTL语言支持)
- 语言切换是切换系统语言,不只本应用
- 切换语言后界面可重启,但后台功能不能停止
实现切换系统语言
- 一般来讲,只更新本应用内的语言,可以采用更新 Configuration 中的 locale 属性的方法。如果修改整个系统的语言,需要调用系统接口,可采用反射调用。
- 修改系统语言需要有系统权限,所以APP需要使用系统签名。可以参考:https://www.jianshu.com/p/b32e559bc003
- AndroidManifest.xml中加上权限:
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/>
- AndroidManifest.xml中根节点上配置成系统用户
android:sharedUserId="android.uid.system"
说明一下,如果在模拟器上运行,只需要系统签名,不需要配置成系统用户,而且几乎任何版本的模拟器都能修改语言成功,虽然用的签名文件与Android模拟器的版本不一致。如果是真机,签名文件要与系统版本一致,另外需要配置成系统用户(有的版本不一定需要)。
- 切换语言代码:
public void updateLanguage(Locale locale) {
try {
Object objIActMag, objActMagNative;
Class clzIActMag = Class.forName("android.app.IActivityManager");
Class clzActMagNative = Class
.forName("android.app.ActivityManagerNative");
//amn = ActivityManagerNative.getDefault();
Method mtdActMagNative$getDefault = clzActMagNative
.getDeclaredMethod("getDefault");
objIActMag = mtdActMagNative$getDefault.invoke(clzActMagNative);
// objIActMag = amn.getConfiguration();
Method mtdIActMag$getConfiguration = clzIActMag
.getDeclaredMethod("getConfiguration");
Configuration config = (Configuration) mtdIActMag$getConfiguration.invoke(objIActMag);
// set the locale to the new value
config.locale = locale;
//持久化 config.userSetLocale = true;
Class clzConfig = Class
.forName("android.content.res.Configuration");
java.lang.reflect.Field userSetLocale = clzConfig
.getField("userSetLocale");
userSetLocale.set(config, true);
//如果有阿拉伯语,必须加上,否则阿拉伯语与其它语言切换时,布局与文字方向不会改变
Method setLayoutDirection = clzConfig
.getDeclaredMethod("setLayoutDirection",Locale.class);
setLayoutDirection.invoke(config,locale);
// 此处需要声明权限:android.permission.CHANGE_CONFIGURATION
// 会重新调用 onCreate();
Class[] clzParams = {Configuration.class};
// objIActMag.updateConfiguration(config);
Method mtdIActMag$updateConfiguration = clzIActMag
.getDeclaredMethod("updateConfiguration", clzParams);
mtdIActMag$updateConfiguration.invoke(objIActMag, config);
BackupManager.dataChanged("com.android.providers.settings");
} catch (Exception e) {
e.printStackTrace();
}
}
调用:
//简体中文
updateLanguage(Locale.SIMPLIFIED_CHINESE);
//英语
updateLanguage(Locale.ENGLISH);
//俄语
updateLanguage(new Locale("RU","ru",""));
//阿拉伯语
updateLanguage(new Locale("ar","SA",""));
//西班牙语
updateLanguage(new Locale("ES","es",""));
切换系统语言,应用重启问题
一般而言,修改系统语言后,会导致所有Activity重启,而不是应用重启,即Application对象、Service对象、全局性质的对话框对象及其它比如一些已经初始化的单例对象会保留。
Activity重启一般是前台的Activity会立即重启,位于后台的Activity在重新打开时会重启(先调用onDestroy,然后生成新的Activity对象时调用onCreate)。
-
Activity重启后会执行恢复动作,恢复之前的状态,所以切换语言而导致Activity重启,必须要做好Activity的状态保存与恢复,如果Activity里面包含有Fragment,还要做好Fragment的状态保存与恢复。
如果不想让Activity重启,可以在AndroidManifest.xml对应的Activity节点加上配置:
android:configChanges="locale|layoutDirection|keyboard"
如果有多个Activity,加了该属性的不会重启,没有加的还是会重启。
Activity不重启,会导致Activity的界面不会自动刷新,需要专门编写相关代码进行刷新,因此不建议使用这种方式。
界面刷新问题
-
配置好国际化各国语言资源目录与文件
如果Activity重启,界面会重新加载,自动到资源目录中对应的语言下加载相应的布局与文字等资源
-
如果Activity不重启,则需要手动刷新界面
一个可行的方法:增加一个BroadcastReceiver,捕捉语言变化广播Intent.ACTION_LOCALE_CHANGED,当系统语言发生变化时,在Activity里刷新界面。
如果有静态变量或者单例对象已加载过string.xml里的文字,则需要手动进行刷新,刷新方法可采用捕捉语言变化广播的方法
如果数据库里存储了一些与多语言相关的文字,也需要及时刷新,刷新方法如上
避免内存泄露
Activity重启后会进行恢复,之前的Activity对象会被系统回收掉,但是如果旧Activity对象被静态对象或者单例对象所引用,而没有及时解除引用,会造成内存泄露,导致旧的Activity对象无法被回收,同时旧Activity对象还存在于内存中,可能也会引起一些问题,Fragment也一样。如何避免:
- 在onCreate/onAttach中添加的监听器或者注册的广播,在onDestroy/onDetach中进行remove
- 慎用单例模式、静态变量
RTL布局支持
可参考:https://juejin.cn/post/6844903491341647879
- AndroidManifest.xml中application节点增加属性:
android:supportsRtl="true"
- 布局中将所有的左右(left/right)布局属性替换成开始结束(start/end)属性,比如:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txt_content"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"/>
-
指定布局中的方向,不随语言而变化
有些布局我们想固定其显示方向为LTR,在阿拉伯语言时也显示成从左到右,可以在布局中设置相应属性:
android:layoutDirection 设置组件的布局方向,默认为android:layoutDirection="locale"
android:textDirection 设置组件文字的方向
-
android:textAlignment 设置组件文字的对齐
比如:
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layoutDirection="ltr"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="20" android:textDirection="ltr"/> </LinearLayout>
-
数字的处理
虽然我们一般用阿拉伯数字,但是也有区分:
西阿拉伯数字:(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
东阿拉伯数字:(٠ - ١ - ٢ - ٣ - ٤ - ٥ - ٦ - ٧ - ٨ - ٩)
波斯数字:(۰ - ۱ - ۲ - ۳ - ۴ - ۵ - ۶ - ۷ - ۸ - ۹)一般常用西阿拉伯数字,但是语言改为阿拉伯语后,数字往往显示成东阿拉伯数字形式,为了避免采用东阿拉伯数字或者波斯数字,格式化字符串与数字时可以增加Locale参数:
//String.format格式化: long seconds = System.currentTimeMillis()/1000; long hour = seconds/3600; long minutes= (seconds - hour*3600)/60; String timeStr = String.format(Locale.US,"%02d:%02d:%02d",hour, minutes, seconds) //格式化数字 long fileSize = 1024*1024; DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); DecimalFormat df = new DecimalFormat("#.00",symbols); String fileSizeString = df.format((double) fileSize / 1048576) + "MB";
网上说可以修改系统达到目的,参考:https://blog.csdn.net/u010083774/article/details/44227929
-
阿拉伯语string.xml中的占位符
阿拉伯与常规是有些区别的,举两个例子:
string.xml(zh-rCN):
<string name="air_quality">空气质量:%1$s</string> <string name="air_index">空气指数:%1$d</string>
string.xml(ar):
<string name="air_quality">s$1%:جودة الهواء</string> <string name="air_index">d$1%:مؤشر الهواء</string>