首先,爲什麼會寫這篇文章呢?那就是今天端午節,苦日子終於熬出頭了,app版本正式交付。O(∩_∩)O哈哈~
進入正題,開發過程中需要動態改變app的語言環境,這個時候就需要用到Android自帶的Locale類。也許會說,app的語言的環境不是系統自動尋找values-的文件嗎?要知道,這只是系統根據自帶語言設置應用資源的搜索策略,即values- → valuse的搜索順序。但是,要考慮到有時候應用的語言環境要區別於系統語言環境,這個時候,代碼中動態改變app語言環境就很有必要。
直接上代碼:
/**
* 切換應用語言環境
*
* @param context
*/
private void loadAppLanguage(Context context, Locale targtLocal) {
//todo 模擬器無法跟隨指定語言環境
if (context == null) {
throw new NullPointerException("loadAppLanguage failed because context is null");
}
lg.e("loadAppLanguage:" + targtLocal);
Configuration config = context.getResources().getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLocale(targtLocal);
} else {
//noinspection deprecation
config.locale = targtLocal;
}
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
Locale.setDefault(targtLocal);
}
so 簡單,有木有~~
好,扔給妹子測試去,我先喝杯吃個水果拼盤先。話說,公司福利還是不錯的哈~
然後,~~~咦,怎麼屏幕旋轉的時候都變成中文了?還有,系統切換語言成繁文的時候,app怎麼也跟着變了?
翻閱相關資料後,發現屏幕旋轉時或者系統語言變更後都需要重新設置。好,上代碼:
//註冊語言變更監聽器
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
lg.e("監聽到語言變化");
loadAppLanguage(context, locale);
}
}, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
關於監聽屏幕旋轉切換時的監聽操作,網上多半是直接重寫Activity的onConfigurationChanged方式,但說實話,這種方法弊端很明顯,如果是老舊代碼遺留的歷史債務,改起來就相當噁心。而且我很討厭到處改代碼,信奉的原則是怎麼偷懶怎麼來。(Application也可以在onConfigurationChanged設置 - 用了,接下來還怎麼裝*?O(∩_∩)O~ 其實在後面會說明,一個單獨的模塊,最好不要讓開發人員做太多的控制,模塊內部把所有的工作都做完最好!)
仔細想想,爲什麼不直接Hook系統的狀態切換呢?分析下,我們只要拿到UI進程的狀態通信類,就可以做自己的工作了。Hook UI線程的原理這裏就按下不表了,感興趣的自己去網上查資料,代碼貼在下面。
/**
* 監聽主線程的Handler對象
*/
public static void hookActivityThreadHandler(Handler.Callback callback) {
if (callback == null) {
lg.e("not support hookActivityThreadHandler because Handler.Callback not define");
return;
}
try {
//由於ActivityThread無法直接訪問
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
//獲取主線程對象
Object activityThreadObj = currentActivityThreadMethod.invoke(null);
//獲取mH字段q
Field mH = activityThreadClass.getDeclaredField("mH");
mH.setAccessible(true);
//獲取Handler
Handler handler = (Handler) mH.get(activityThreadObj);
//獲取原始的mCallBack字段
Field mCallBack = Handler.class.getDeclaredField("mCallback");
mCallBack.setAccessible(true);
//這裏設置自定義實現的CallBack接口對象
mCallBack.set(handler, callback);
} catch (Exception e) {
e.printStackTrace();
}
}
好了,入口代碼如下,
public void setAppLocale(final Context context, final Locale locale) {
//避免多進程重複調用
if (!TempUtils.isUIPid(context)) {
return;
}
loadAppLanguage(context, locale);
//註冊語言變更監聽器
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
lg.e("監聽到語言變化");
loadAppLanguage(context, locale);
}
}, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
//監聽橫豎屏切換
HookUtils.hookActivityThreadHandler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//118:@link ActivityThread - CONFIGURATION_CHANGED
if (msg.what == 118) {
if (msg.obj instanceof Configuration) {
lg.e("檢測到屏幕旋轉,重置app語言環境");
loadAppLanguage(context, locale);
}
}
return false;
}
});
}
只需要在Application的入口地方直接調用
new LanguageManager().setAppLocale(this,Locale.ENGLISH);
,剩下的所有工作全部自動幫你管理。
小Tips:
1.其實動態設置語言這個東西可以用的很活。比如現在需要把頁面佈局改下樣式然後快速上線,但是又不想對原有的佈局以及資源文件進行改動,這個時候完全可以自定義莫須有的語言環境,然後。。。你懂得!!!