语言切换的三种方法

Android多语言切换
Android对国际化与多语言切换已经做得不错了,一个应用只要命名相应语系的values-[language]文件夹,通过“设置”→“语言&键盘”→“选择语言”即可实现应用多种语言的切换。
但如何在应用里自己实现?搜索过发现网上有如下的做法:
  1. Resourcesres=getResources();
  2. Configurationconfig=res.getConfiguration();
  3. config.locale=locale;
  4. DisplayMetricsdm=res.getDisplayMetrics();
  5. res.updateConfiguration(config,dm);

亲测,不成功。好吧,程序员又到了自力更生的时候了。下面开始讲应用多语言切换的三种方法。

先上效果图:

English

前两种方法的原理即在应用里实现“选择语言”。通过查看源码,其核心代码为:

  1. IActivityManageriActMag=ActivityManagerNative.getDefault();
  2. try{
  3. Configurationconfig=iActMag.getConfiguration();
  4. config.locale=locale;
  5. //此处需要声明权限:android.permission.CHANGE_CONFIGURATION
  6. //会重新调用onCreate();
  7. iActMag.updateConfiguration(config);
  8. }catch(RemoteExceptione){
  9. e.printStackTrace();
  10. }
  11. PS:感谢曾阳的帮助。
可以发现IActivityManager与ActivityManagerNative都是非公开类。如何调用?第一种是API欺骗,第二种是使用Java反射机制。
1. API欺骗
烧制到手机中的android.jar包含了Android所需的各种类与方法;而供开发者使用的android.jar只是其中的一部分。API欺骗是指在应用中去模拟未公开的类和方法让应用编译通过并生成APK,然而在应用实际运行中调用的却仍是烧制到手机中真实的android.jar。

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

工程结构图:

代码:

  1. ActivityManagerNative.java
  2. packageandroid.app;
  3. /**
  4. *@authorSodinoE-mail:sodinoopen@hotmail.com
  5. *@versionTime:2011-7-10上午11:37:01
  6. */
  7. publicabstractclassActivityManagerNative{
  8. publicstaticIActivityManagergetDefault(){
  9. returnnull;
  10. }
  11. }
  12. IActivityManager.java
  13. packageandroid.app;
  14. importandroid.content.res.Configuration;
  15. importandroid.os.RemoteException;
  16. /**
  17. *@authorSodinoE-mail:sodinoopen@hotmail.com
  18. *@versionTime:2011-7-10上午11:37:46
  19. */
  20. publicabstractinterfaceIActivityManager{
  21. publicabstractConfigurationgetConfiguration()throwsRemoteException;
  22. publicabstractvoidupdateConfiguration(ConfigurationparamConfiguration)
  23. throwsRemoteException;
  24. }
实现模拟了这两个类后,即可正常使用上面提到的转换语系的核心代码了。

2. Java反射机制
不多说了,Java反射机制入门教程:
http://java.sun.com/developer/technicalArticles/ALT/Reflection/index.html
之前写过的几个使用Java反射的例子:
[Android]获取未安装的APK图标(原创非转帖)
http://blog.youkuaiyun.com/sodino/article/details/6215224
[Android]挂断、接听电话
http://blog.youkuaiyun.com/sodino/article/details/6181610

直接上代码:

  1. privatevoidupdateLanguage(Localelocale){
  2. Log.d("ANDROID_LAB",locale.toString());
  3. try{
  4. ObjectobjIActMag,objActMagNative;
  5. ClassclzIActMag=Class.forName("android.app.IActivityManager");
  6. ClassclzActMagNative=Class.forName("android.app.ActivityManagerNative");
  7. MethodmtdActMagNative$getDefault=clzActMagNative.getDeclaredMethod("getDefault");
  8. //IActivityManageriActMag=ActivityManagerNative.getDefault();
  9. objIActMag=mtdActMagNative$getDefault.invoke(clzActMagNative);
  10. //Configurationconfig=iActMag.getConfiguration();
  11. MethodmtdIActMag$getConfiguration=clzIActMag.getDeclaredMethod("getConfiguration");
  12. Configurationconfig=(Configuration)mtdIActMag$getConfiguration.invoke(objIActMag);
  13. config.locale=locale;
  14. //iActMag.updateConfiguration(config);
  15. //此处需要声明权限:android.permission.CHANGE_CONFIGURATION
  16. //会重新调用onCreate();
  17. Class[]clzParams={Configuration.class};
  18. MethodmtdIActMag$updateConfiguration=clzIActMag.getDeclaredMethod(
  19. "updateConfiguration",clzParams);
  20. mtdIActMag$updateConfiguration.invoke(objIActMag,config);
  21. }catch(Exceptione){
  22. e.printStackTrace();
  23. }
  24. }
实际运行后,发现对当前系统设置了新的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是这个的格式:

  1. <stringname="app_name">语言应用</string>
对应R文件会生成一个id指代该string
  1. publicstaticfinalclassstring{
  2. publicstaticfinalintapp_name=0x7f050001;
  3. }

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

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

详细实现过程见下面三个工程中:
LanguageApp_APICheat
LanguageApp_Reflection
LanguageApp_Sodino
(PS:不要问我为什么下载的工程在IDE中为什么无法直接使用,为什么打开是乱码红叉一大堆,既然是程序员,遇到问题是不是也该自己多思考思考呢。)
本文内容归优快云博客博主Sodino 所有
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值