android语言三

来源:互联网 发布:淘宝网上怎么开店铺 编辑:程序博客网 时间:2024/05/18 03:55

[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