您的位置:首页 > 移动开发 > Android开发

[Android]应用语言切换的三种方法

2011-07-15 16:36 381 查看
Android对国际化与多语言切换已经做得不错了,一个应用只要命名相应语系的values-[language]文件夹,通过“设置”→“语言&键盘”→“选择语言”即可实现应用多种语言的切换。
但如何在应用里自己实现?搜索过发现网上有如下的做法:
view plaincopy to clipboardprint?Resources res = getResources();
Configuration config = res.getConfiguration();
config.locale = locale;
DisplayMetrics dm = res.getDisplayMetrics();
res.updateConfiguration(config, dm);
view plaincopy to clipboardprint?IActivityManager iActMag = ActivityManagerNative.getDefault();
try {
Configuration config = iActMag.getConfiguration();
config.locale = locale;
// 此处需要声明权限:android.permission.CHANGE_CONFIGURATION
// 会重新调用 onCreate();
iActMag.updateConfiguration(config);
} catch (RemoteException e) {
e.printStackTrace();
}
PS:感谢 曾阳 的帮助。
IActivityManager iActMag = ActivityManagerNative.getDefault();try {Configuration config = iActMag.getConfiguration();config.locale = locale;// 此处需要声明权限:android.permission.CHANGE_CONFIGURATION// 会重新调用 onCreate();iActMag.updateConfiguration(config);} catch (RemoteException e) {e.printStackTrace();}PS:感谢 曾阳 的帮助。
可以发现IActivityManager与ActivityManagerNative都是非公开类。如何调用?第一种是API欺骗,第二种是使用Java反射机制。
1. API欺骗
烧制到手机中的android.jar包含了Android所需的各种类与方法;而供开发者使用的android.jar只是其中的一部分。API欺骗是指在应用中去模拟未公开的类和方法让应用编译通过并生成APK,然而在应用实际运行中调用的却仍是烧制到手机中真实的android.jar。

通过核心代码可以看到我们要模拟的是ActivityManagerNative中的一个方法getDefault()和IActivityManager中的两个方法getConfiguration()与updateConfiguration(config)。参照源码,应用的工程结构图及代码模拟如下:

工程结构图:



代码:

view plaincopy to clipboardprint?ActivityManagerNative.java
package android.app;

/**
* @author Sodino E-mail:sodinoopen@hotmail.com
* @version Time:2011-7-10 上午11:37:01
*/
public abstract class ActivityManagerNative {
public static IActivityManager getDefault() {
return null;
}
}

IActivityManager.java
package android.app;

import android.content.res.Configuration;
import android.os.RemoteException;

/**
* @author Sodino E-mail:sodinoopen@hotmail.com
* @version Time:2011-7-10 上午11:37:46
*/
public abstract interface IActivityManager {
public abstract Configuration getConfiguration() throws RemoteException;

public abstract void updateConfiguration(Configuration paramConfiguration)
throws RemoteException;
}
view plaincopy to clipboardprint?private void updateLanguage(Locale locale) {
Log.d("ANDROID_LAB", locale.toString());
try {
Object objIActMag, objActMagNative;
Class clzIActMag = Class.forName("android.app.IActivityManager");
Class clzActMagNative = Class.forName("android.app.ActivityManagerNative");
Method mtdActMagNative$getDefault = clzActMagNative.getDeclaredMethod("getDefault");
// IActivityManager iActMag = ActivityManagerNative.getDefault();
objIActMag = mtdActMagNative$getDefault.invoke(clzActMagNative);
// Configuration config = iActMag.getConfiguration();
Method mtdIActMag$getConfiguration = clzIActMag.getDeclaredMethod("getConfiguration");
Configuration config = (Configuration) mtdIActMag$getConfiguration.invoke(objIActMag);
config.locale = locale;
// iActMag.updateConfiguration(config);
// 此处需要声明权限:android.permission.CHANGE_CONFIGURATION
// 会重新调用 onCreate();
Class[] clzParams = { Configuration.class };
Method mtdIActMag$updateConfiguration = clzIActMag.getDeclaredMethod(
"updateConfiguration", clzParams);
mtdIActMag$updateConfiguration.invoke(objIActMag, config);
} catch (Exception e) {
e.printStackTrace();
}
}
private void updateLanguage(Locale locale) {Log.d("ANDROID_LAB", locale.toString());try {Object objIActMag, objActMagNative;Class clzIActMag = Class.forName("android.app.IActivityManager");Class clzActMagNative = Class.forName("android.app.ActivityManagerNative");Method mtdActMagNative$getDefault = clzActMagNative.getDeclaredMethod("getDefault");// IActivityManager iActMag = ActivityManagerNative.getDefault();objIActMag = mtdActMagNative$getDefault.invoke(clzActMagNative);// Configuration config = iActMag.getConfiguration();Method mtdIActMag$getConfiguration = clzIActMag.getDeclaredMethod("getConfiguration");Configuration config = (Configuration) mtdIActMag$getConfiguration.invoke(objIActMag);config.locale = locale;// iActMag.updateConfiguration(config);// 此处需要声明权限:android.permission.CHANGE_CONFIGURATION// 会重新调用 onCreate();Class[] clzParams = { Configuration.class };Method mtdIActMag$updateConfiguration = clzIActMag.getDeclaredMethod("updateConfiguration", clzParams);mtdIActMag$updateConfiguration.invoke(objIActMag, config);} catch (Exception e) {e.printStackTrace();}}
实际运行后,发现对当前系统设置了新的Locale后,不单自己的应用语系改变了,系统所有的应用语系都改变了。这肯定是不合理的。有一个解决办法是在应用界面退出前再次对系统设置成碑的Locale,不过个人不喜欢这样的办法,加之调用updateConfiguration()方法后,整个Activity会重新onCreate(),这个考虑Activity的生命周期可有点费劲了。于是有了下面这第三种方法。

3. 自己转换语系(哈哈,这个名字很现实啊)
动手实现嘛,啥都系统弄好了,那程序员的存在还有什么意义呢。
自己转换语系有点麻烦,先看工程结构图:



values/strings.xml与xml/english.xml的内容是相同的;values-zh-rCN/strings.xml与xml/chinese.xml的内容也是相同的。出现这样的冗余是因为生成APK时values下的内容都打到rasc去了,读取不了了。

自己实现语系的转换需要考虑到:
3.1 R.xxxxx.id与对应语系中文本串的对应(需要特别考虑到R.array.string字符串数组)。
3.2 解析xml。
3.3 设置语系后,所有界面元素的手动刷新。

在xml中声明一个string是这个的格式:

view plaincopy to clipboardprint?<string name="app_name">语言应用</string>
view plaincopy to clipboardprint?public static final class string {
public static final int app_name=0x7f050001;
}
public static final class string {        public static final int app_name=0x7f050001;    }

3.1的问题就是如何实现id与string的匹配,解决方法为:
view plaincopy to clipboardprint?Resources res = context.getResources();
String pkg = context.getPackageName();
String tag = "app_name";
int idTag = res.getIdentifier(tag, "string", pkg);
Resources res = context.getResources();String pkg = context.getPackageName();String tag = "app_name";int idTag = res.getIdentifier(tag, "string", pkg); 3.2 解析XML
这儿要用到一个新的工具了:XmlResourceParser,解析过程有点绕,但比SAX简单些。具体细节见LanguageApp_Sodino工程中的代码吧。

3.3 手动刷新界面。
要获取所有涉及到语系更新组件的索引逐一更新,体力活儿,细心点花点力气也可实现。

详细实现过程见下面三个工程中:
LanguageApp_APICheat
LanguageApp_Reflection
LanguageApp_Sodino
(PS:不要问我为什么下载的工程在IDE中为什么无法直接使用,为什么打开是乱码红叉一大堆,既然是程序员,遇到问题是不是也该自己多思考思考呢。)

本文内容归CSDN博客博主Sodino 所有
转载请注明出处:/article/2637279.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: