[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