Android 切換系統語言原始碼分析
以前瞭解Android的多語言實現很簡單,可以在不同的語言環境下使用不同的資源,就做好相應的語言適配就好,但是一直沒有實際使用過。 最近公司的專案要用到多國語言切換,並且還是和手機上系統設定裡面的語言切換功能一樣,於是就上網查了下資料。一般都是在應用類實現多國語言切換,這個是很簡單。而我想切換整個系統的語言。由於谷歌沒有把系統設定裡面的介面給開放出來,所以就只好去檢視它的原始碼了~
android語言切換是在:
packages/apps/Settings/com/android/settings/LocalePicker.java
的updateLocale()函式中呼叫,原始碼如下:
/** * Requests the system to update the system locale. Note that the system looks halted for a while during the Locale migration, so the caller need to take care of it. */ public static void updateLocale(Locale locale) { try { IActivityManager am = ActivityManagerNative.getDefault(); Configuration config = am.getConfiguration(); config.locale = locale; // indicate this isn't some passing default - the user wants this remembered config.userSetLocale = true; am.updateConfiguration(config); // Trigger the dirty bit for the Settings Provider. BackupManager.dataChanged("com.android.providers.settings"); } catch (RemoteException e) { // Intentionally left blank } }
從註釋可以看出, 只要本地local改變就會呼叫該函式. 檢視ActivityManagerNative的getDefault()可以看到, 該函式返回的是遠端服務物件ActivityManagerServices.java在本地的一個代理. 最終呼叫的是ActivityManagerService.java中的updateConfiguration()函式.
public void updateConfiguration(Configuration values) { enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, "updateConfiguration()"); synchronized(this) { if (values == null && mWindowManager != null) { // sentinel: fetch the current configuration from the window manager values = mWindowManager.computeNewConfiguration(); } if (mWindowManager != null) { mProcessList.applyDisplaySize(mWindowManager); } final long origId = Binder.clearCallingIdentity(); if (values != null) { Settings.System.clearConfiguration(values); } updateConfigurationLocked(values, null, false, false); Binder.restoreCallingIdentity(origId); } }
該函式, 首先進行的是許可權的校驗. 然後呼叫updateConfigurationLocked()函式.
/** * Do either or both things: (1) change the current configuration, and (2) * make sure the given activity is running with the (now) current * configuration. Returns true if the activity has been left running, or * false if <var>starting</var> is being destroyed to match the new * configuration. * @param persistent TODO */ public boolean updateConfigurationLocked(Configuration values, ActivityRecord starting, boolean persistent, boolean initLocale) { int changes = 0; boolean kept = true; if (values != null) { Configuration newConfig = new Configuration(mConfiguration); changes = newConfig.updateFrom(values); if (changes != 0) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { Slog.i(TAG, "Updating configuration to: " + values); } EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes); if (values.locale != null && !initLocale) { saveLocaleLocked(values.locale, !values.locale.equals(mConfiguration.locale), values.userSetLocale, values.simSetLocale); } mConfigurationSeq++; if (mConfigurationSeq <= 0) { mConfigurationSeq = 1; } newConfig.seq = mConfigurationSeq; mConfiguration = newConfig; Slog.i(TAG, "Config changed: " + newConfig); final Configuration configCopy = new Configuration(mConfiguration); AttributeCache ac = AttributeCache.instance(); if (ac != null) { ac.updateConfiguration(configCopy); } // Make sure all resources in our process are updated // right now, so that anyone who is going to retrieve // resource values after we return will be sure to get // the new ones. This is especially important during // boot, where the first config change needs to guarantee // all resources have that config before following boot // code is executed. mSystemThread.applyConfigurationToResources(configCopy); if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) { Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG); msg.obj = new Configuration(configCopy); mHandler.sendMessage(msg); } for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); try { if (app.thread != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc " + app.processName + " new config " + mConfiguration); app.thread.scheduleConfigurationChanged(configCopy); } } catch (Exception e) { } } Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, false, false, MY_PID, Process.SYSTEM_UID); if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) { broadcastIntentLocked(null, null, new Intent(Intent.ACTION_LOCALE_CHANGED), null, null, 0, null, null, null, false, false, MY_PID, Process.SYSTEM_UID); } } } if (changes != 0 && starting == null) { // If the configuration changed, and the caller is not already // in the process of starting an activity, then find the top // activity to check if its configuration needs to change. starting = mMainStack.topRunningActivityLocked(null); } if (starting != null) { kept = mMainStack.ensureActivityConfigurationLocked(starting, changes); // And we need to make sure at this point that all other activities // are made visible with the correct configuration. mMainStack.ensureActivitiesVisibleLocked(starting, changes); } if (values != null && mWindowManager != null) { mWindowManager.setNewConfiguration(mConfiguration); } return kept; }
整個語言切換就在這個函式中完成. 咋一看似乎沒感覺到該函式做了哪些事情. 我們首先來看註釋: Do either or both things: (1) change the current configuration, and (2)
make sure the given activity is running with the (now) current. configuration大概意思是: 這個函式做了兩件事情. (1). 改變當前的configuration. 意思就是讓改變的configuration更新到當前configuration. (2) 確保所有正在執行的activity都能更新改變後的configuration.(這點是關鍵.) . 我們按照這個思路看看android是如何更新configuration. 檢視程式碼 , 首先看到 這個函式首先判斷values是否為空, 這裡values肯定不為空的, 然後changes = newConfig.updateFrom(values); 我們看看updateFrom做了什麼操作。
/** * Copy the fields from delta into this Configuration object, keeping * track of which ones have changed. Any undefined fields in * <var>delta</var> are ignored and not copied in to the current * Configuration. * @return Returns a bit mask of the changed fields, as per * {@link #diff}. */ public int updateFrom(Configuration delta) { int changed = 0; ... if (delta.locale != null && (locale == null || !locale.equals(delta.locale))) { changed |= ActivityInfo.CONFIG_LOCALE; locale = delta.locale != null ? (Locale) delta.locale.clone() : null; textLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(locale); } if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0))) { userSetLocale = true; changed |= ActivityInfo.CONFIG_LOCALE; } ... return changed; }
因為語言改變了, 那麼 (!locale.equals(delta.locale)) 是true. changed 大於0, 然後return changed. 回到ActivityManagerService.java的updateConfigurationLocked函式, 因為changed不為0 , 所以走if這個流程. 繼續看程式碼。
for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); try { if (app.thread != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc " + app.processName + " new config " + mConfiguration); app.thread.scheduleConfigurationChanged(configCopy); } } catch (Exception e) { } }
首先看到的是mLurProcesses 是ArrayList型別. LRU : Least Recently Used儲存所有執行過的程式. ProcessRecord程式類, 一個apk檔案執行時會對應一個程式. app.thread. 此處的thread代表的是ApplicationThreadNative.java型別. 然後呼叫其scheduleConfigurationChanged(); 檢視該函式。
public final void scheduleConfigurationChanged(Configuration config) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); config.writeToParcel(data, 0); mRemote.transact(SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); }
又是通過binder呼叫, 所以 , binder在android中是一個很重要的概念. 此處遠端呼叫的是ActivityThread.java中的私有內部內ApplicationThread。
private class ApplicationThread extends ApplicationThreadNative { private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s"; private static final String ONE_COUNT_COLUMN = "%21s %8d"; private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d"; private static final String TWO_COUNT_COLUMNS_DB = "%21s %8d %21s %8d"; private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s"; ... public void scheduleConfigurationChanged(Configuration config) { updatePendingConfiguration(config); queueOrSendMessage(H.CONFIGURATION_CHANGED, config); } ... }
而ApplicationThread中的handler的CONFIGURATION_CHANGED是呼叫handleConfigurationChanged()。
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { ArrayList<ComponentCallbacks2> callbacks = null; ... ... applyConfigurationToResourcesLocked(config, compat); ... callbacks = collectComponentCallbacksLocked(false, config); ... if (callbacks != null) { final int N = callbacks.size(); for (int i=0; i<N; i++) { performConfigurationChanged(callbacks.get(i), config); } }
這個函式首先是呼叫applyConfigurationToResourcesLocked(). 看函式名大概可以猜想到: 將configuration應用到resources.這裡configuration改變的是local 本地語言. 那而resources資源包含語言包嗎?
final boolean applyConfigurationToResourcesLocked(Configuration config, CompatibilityInfo compat) { int changes = mResConfiguration.updateFrom(config); DisplayMetrics dm = getDisplayMetricsLocked(null, true); if (compat != null && (mResCompatibilityInfo == null || !mResCompatibilityInfo.equals(compat))) { mResCompatibilityInfo = compat; changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; } ... Resources.updateSystemConfiguration(config, dm, compat); ... Iterator<WeakReference<Resources>> it = mActiveResources.values().iterator(); while (it.hasNext()) { WeakReference<Resources> v = it.next(); Resources r = v.get(); if (r != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " + r + " config to: " + config); r.updateConfiguration(config, dm, compat); //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { //Slog.i(TAG, "Removing old resources " + v.getKey()); it.remove(); } } return changes != 0; }
Resources.updateSystemConfiguration()清除一部分系統資源, 並且將config更新到Resources, 而Resources包含了一個AssetManager物件, 該物件的核心實現是在AssetManager.cpp中完成的. 然後迴圈清空mActivityResources資源. 再回到handleConfigurationChanged()函式, 執行完updateSystemConfiguration後, 會迴圈該程式的所有activity:
if (callbacks != null) { final int N = callbacks.size(); for (int i=0; i<N; i++) { performConfigurationChanged(callbacks.get(i), config); } }
再來看performConfigurationChanged的實現:
private final void performConfigurationChanged( ComponentCallbacks2 cb, Configuration config) { // Only for Activity objects, check that they actually call up to their // superclass implementation. ComponentCallbacks2 is an interface, so // we check the runtime type and act accordingly. Activity activity = (cb instanceof Activity) ? (Activity) cb : null; if (activity != null) { activity.mCalled = false; } boolean shouldChangeConfig = false; if ((activity == null) || (activity.mCurrentConfig == null)) { shouldChangeConfig = true; } else { // If the new config is the same as the config this Activity // is already running with then don't bother calling // onConfigurationChanged int diff = activity.mCurrentConfig.diff(config); if (diff != 0) { // If this activity doesn't handle any of the config changes // then don't bother calling onConfigurationChanged as we're // going to destroy it. if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) { shouldChangeConfig = true; } } } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb + ": shouldChangeConfig=" + shouldChangeConfig); if (shouldChangeConfig) { cb.onConfigurationChanged(config); if (activity != null) { if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + activity.getLocalClassName() + " did not call through to super.onConfigurationChanged()"); } activity.mConfigChangeFlags = 0; activity.mCurrentConfig = new Configuration(config); } } }
該函式判斷configuration是否改變, 如果改變那麼shouldChangeConfig為true. 然後呼叫activity的onConfigurationChange(config);
/** * Called by the system when the device configuration changes while your * activity is running. Note that this will <em>only</em> be called if * you have selected configurations you would like to handle with the * {@link android.R.attr#configChanges} attribute in your manifest. If * any configuration change occurs that is not selected to be reported * by that attribute, then instead of reporting it the system will stop * and restart the activity (to have it launched with the new * configuration). * * <p>At the time that this function has been called, your Resources * object will have been updated to return resource values matching the * new configuration. * * @param newConfig The new device configuration. */ public void onConfigurationChanged(Configuration newConfig) { mCalled = true; mFragments.dispatchConfigurationChanged(newConfig); if (mWindow != null) { // Pass the configuration changed event to the window mWindow.onConfigurationChanged(newConfig); } if (mActionBar != null) { // Do this last; the action bar will need to access // view changes from above. mActionBar.onConfigurationChanged(newConfig); } }
檢視註釋, 大概意思是: 如果你的activity執行 , 裝置資訊有改變(即configuration改變)時由系統呼叫. 如果你在manifest.xml中配置了configChnages屬性則表示有你自己來處理configuration change. 否則就重啟當前這個activity. 而重啟之前, 舊的resources已經被清空, 那麼就會裝載新的資源, 整個過程就完成了語言切換後 , 能夠讓所有app使用新的語言。
上面這些就是對Android 系統裡面的語言切換進行了原始碼分析,就先分析到這裡;有些東西我也不是很看懂,能力有限~
相關文章
- 切換Windows的系統語言Windows
- Android 系統原始碼-1:Android 系統啟動流程原始碼分析Android原始碼
- Android 實現APP可切換多語言AndroidAPP
- Android系統原始碼分析之-ContentProviderAndroid原始碼IDE
- Android系統原始碼分析-事件收集Android原始碼事件
- Android系統原始碼分析-Broadcast傳送Android原始碼AST
- C語言學生管理系統原始碼C語言原始碼
- Android系統原始碼分析--Service啟動流程Android原始碼
- Android系統原始碼分析–Service啟動流程Android原始碼
- win10系統控制皮膚中沒有語言項如何切換語言快捷鍵Win10
- Android系統原始碼分析--Activity啟動過程Android原始碼
- Go語言——sync.Map原始碼分析Go原始碼
- Go語言—sync.Cond原始碼分析Go原始碼
- Android系統原始碼分析--View繪製流程之-inflateAndroid原始碼View
- Android系統原始碼分析–View繪製流程之-setContentViewAndroid原始碼View
- Android系統原始碼分析--View繪製流程之-setContentViewAndroid原始碼View
- 直播商城原始碼,實現系統的日間/夜間模式切換原始碼模式
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- Android系統原始碼分析–Zygote和SystemServer啟動過程Android原始碼GoServer
- 直播商城系統原始碼,播放器aliPlayer自定義清晰度切換原始碼播放器
- windows怎麼切換回蘋果系統(電腦雙系統切換系統方法)Windows蘋果
- mac電腦如何切換雙系統,雙系統該怎麼切換Mac
- iOS 多國語言本地化與App內語言切換(Swift)iOSAPPSwift
- Android系統原始碼目錄解析Android原始碼
- Android Choreographer 原始碼分析Android原始碼
- iOS開發之APP內部切換語言iOSAPP
- 閱讀APP原始碼,瞭解Android studio觸控事件,切換圖片APP原始碼Android事件
- Android系統架構與系統原始碼目錄Android架構原始碼
- [原始碼分析] 定時任務排程框架 Quartz 之 故障切換原始碼框架quartz
- Android系統原始碼分析團體專案BeesAndroid正式上線啦Android原始碼
- Android系統原始碼剖析-事件分發Android原始碼事件
- [原始碼和文件分享]C語言做了一個外賣管理系統原始碼C語言
- PHP語言技術開發的手術麻醉管理系統原始碼PHP原始碼
- 醫院資訊系統原始碼(HIS)java語言B/S架構原始碼Java架構
- 影片直播系統原始碼,C語言實現圖片合成功能原始碼C語言
- Android FrameWork學習(二)Android系統原始碼除錯AndroidFramework原始碼除錯
- Android中IntentService原始碼分析AndroidIntent原始碼
- android IO Prefetch原始碼分析Android原始碼
- Android開源原始碼分析Android原始碼