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

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

            分类:                Android 273人阅读 评论(0) 收藏 举报
    Android对国际化与多语言切换已经做得不错了,一个应用只要命名相应语系的values-[language]文件夹,通过“设置”→“语言&键盘”→“选择语言”即可实现应用多种语言的切换。   
    但如何在应用里自己实现?搜索过发现网上有如下的做法:
  1. Resources res = getResources(); 
  2. Configuration config = res.getConfiguration(); 
  3. config.locale = locale; 
  4. DisplayMetrics dm = res.getDisplayMetrics(); 
  5. res.updateConfiguration(config, dm); 
         Resources res = getResources();
         Configuration config = res.getConfiguration();
         config.locale = locale;
         DisplayMetrics dm = res.getDisplayMetrics();
         res.updateConfiguration(config, dm);
 
    亲测,不成功。好吧,程序员又到了自力更生的时候了。下面开始讲应用多语言切换的三种方法。
   
    先上效果图:

English 

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

  1. IActivityManager iActMag = ActivityManagerNative.getDefault(); 
  2. try
  3.     Configuration config = iActMag.getConfiguration(); 
  4.     config.locale = locale; 
  5.     // 此处需要声明权限:android.permission.CHANGE_CONFIGURATION 
  6.     // 会重新调用 onCreate(); 
  7.     iActMag.updateConfiguration(config); 
  8. } catch (RemoteException e) { 
  9.     e.printStackTrace(); 
  10. 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)。参照源码,应用的工程结构图及代码模拟如下:
   
    工程结构图:

代码:

  1. ActivityManagerNative.java 
  2. package android.app; 
  3.  
  4. /**
  5. * @author Sodino E-mail:sodinoopen@hotmail.com
  6. * @version Time:2011-7-10 上午11:37:01
  7. */ 
  8. public abstract class ActivityManagerNative { 
  9.     public static IActivityManager getDefault() { 
  10.         return null
  11.     } 
  12.  
  13. IActivityManager.java 
  14. package android.app; 
  15.  
  16. import android.content.res.Configuration; 
  17. import android.os.RemoteException; 
  18.  
  19. /**
  20. * @author Sodino E-mail:sodinoopen@hotmail.com
  21. * @version Time:2011-7-10 上午11:37:46
  22. */ 
  23. public abstract interface IActivityManager { 
  24.     public abstract Configuration getConfiguration() throws RemoteException; 
  25.  
  26.     public abstract void updateConfiguration(Configuration paramConfiguration) 
  27.             throws RemoteException; 
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;
}
    实现模拟了这两个类后,即可正常使用上面提到的转换语系的核心代码了。

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

  1. private void updateLanguage(Locale locale) { 
  2.     Log.d("ANDROID_LAB", locale.toString()); 
  3.     try
  4.         Object objIActMag, objActMagNative; 
  5.         Class clzIActMag = Class.forName("android.app.IActivityManager"); 
  6.         Class clzActMagNative = Class.forName("android.app.ActivityManagerNative"); 
  7.         Method mtdActMagNative$getDefault = clzActMagNative.getDeclaredMethod("getDefault"); 
  8.         // IActivityManager iActMag = ActivityManagerNative.getDefault(); 
  9.         objIActMag = mtdActMagNative$getDefault.invoke(clzActMagNative); 
  10.         // Configuration config = iActMag.getConfiguration(); 
  11.         Method mtdIActMag$getConfiguration = clzIActMag.getDeclaredMethod("getConfiguration"); 
  12.         Configuration config = (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.         Method mtdIActMag$updateConfiguration = clzIActMag.getDeclaredMethod( 
  19.                 "updateConfiguration", clzParams); 
  20.         mtdIActMag$updateConfiguration.invoke(objIActMag, config); 
  21.     } catch (Exception e) { 
  22.         e.printStackTrace(); 
  23.     } 
	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是这个的格式:

  1. <string name="app_name">语言应用</string> 
    <string name="app_name">语言应用</string>
    对应R文件会生成一个id指代该string
  1. public static final class string { 
  2.     public static final int app_name=0x7f050001
    public static final class string {
        public static final int app_name=0x7f050001;
    }

    3.1的问题就是如何实现id与string的匹配,解决方法为:
  1. Resources res = context.getResources(); 
  2. String pkg = context.getPackageName(); 
  3. String tag = "app_name"
  4. 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中为什么无法直接使用,为什么打开是乱码红叉一大堆,既然是程序员,遇到问题是不是也该自己多思考思考呢。)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值