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

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;

}

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的生命周期可有点费劲了。于是有了下面这第三种方法。

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>

<string name="app_name">语言应用</string> 对应R文件会生成一个id指代该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

更多相关文章

  1. 【Android(安卓)Developers Training】 16. 暂停和恢复一个Activ
  2. Android(安卓)调用系统相机拍照并获取图片
  3. android与H5交互方法简介
  4. Unity调用Android。。。哈哈。。可以干坏事啦。。。。。。
  5. android APK 调用G—sensor驱动的过程
  6. 禁止其他应用访问自己的组件
  7. Android日常开发(18)android 调用 vue methods 方法,提示"Uncaught
  8. Android四大组件基本介绍及其生命周期
  9. android suspend and resume

随机推荐

  1. 【android】在Eclipse在联想引jar包源代
  2. android蓝牙键盘调试记录
  3. android native C 和 java 通信
  4. android启动模式singleInstance的疑惑
  5. Android(安卓)BroadcastReceiver实例Demo
  6. android apk编译时可以找到的资源,但在运
  7. 基于FFmpeg的音频编码(PCM数据编码成AAC a
  8. java搭建本地服务器,android客户端访问,tom
  9. android 目录结构说明
  10. android 横竖屏幕切换