android語言切換是在packages/apps/Settings/com/android/settings/LocalePicker.java的updateLocale()函數(shù)中調(diào)用.
-
-
-
-
- public static void updateLocale(Locale locale) {
- try {
- IActivityManager am = ActivityManagerNative.getDefault();
- Configuration config = am.getConfiguration();
-
- config.locale = locale;
-
-
- config.userSetLocale = true;
-
- am.updateConfiguration(config);
-
- BackupManager.dataChanged("com.android.providers.settings");
- } catch (RemoteException e) {
-
- }
- }
從注釋可以看出, 只要本地local改變就會調(diào)用該函數(shù). 查看ActivityManagerNative的getDefault()可以看到, 該函數(shù)返回的是遠程服務對象ActivityManagerServices.java在本地的一個代理. 最終調(diào)用的是ActivityManagerService.java中的updateConfiguration()函數(shù).
- public void updateConfiguration(Configuration values) {
- enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
- "updateConfiguration()");
-
- synchronized(this) {
- if (values == null && mWindowManager != null) {
-
- 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);
- }
- }
該函數(shù), 首先進行的是權(quán)限的校驗. 然后調(diào)用updateConfigurationLocked()函數(shù).
-
-
-
-
-
-
-
-
- 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);
- }
-
-
-
-
-
-
-
-
- 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) {
-
-
-
- starting = mMainStack.topRunningActivityLocked(null);
- }
-
- if (starting != null) {
- kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);
-
-
- mMainStack.ensureActivitiesVisibleLocked(starting, changes);
- }
-
- if (values != null && mWindowManager != null) {
- mWindowManager.setNewConfiguration(mConfiguration);
- }
-
- return kept;
- }
整個語言切換就在這個函數(shù)中完成. 咋一看似乎沒感覺到該函數(shù)做了哪些事情. 我們首先來看注釋: Do either or both things: (1) change the current configuration, and (2)
make sure the given activity is running with the (now) current. configuration大概意思是: 這個函數(shù)做了兩件事情. (1). 改變當前的configuration. 意思就是讓改變的configuration更新到當前configuration. (2) 確保所有正在運行的activity都能更新改變后的configuration.(這點是關鍵.) . 我們按照這個思路看看android是如何更新configuration. 查看代碼 , 首先看到 這個函數(shù)首先判斷values是否為空, 這里values肯定不為空的, 然后changes = newConfig.updateFrom(values); 我們看看updateFrom做了什么操作.
-
-
-
-
-
-
-
-
- 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函數(shù), 因為changed不為0 , 所以走if這個流程. 繼續(xù)看代碼
- 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<ProcessRecord>類型. LRU : Least Recently Used保存所有運行過的進程. ProcessRecord進程類, 一個apk文件運行時會對應一個進程. app.thread. 此處的thread代表的是ApplicationThreadNative.java類型. 然后調(diào)用其scheduleConfigurationChanged(); 查看該函數(shù)
- 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調(diào)用, 所以 , binder在android中是一個很重要的概念. 此處遠程調(diào)用的是ActivityThread.java中的私有內(nèi)部內(nèi)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是調(diào)用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);
- }
- }
這個函數(shù)首先是調(diào)用applyConfigurationToResourcesLocked(). 看函數(shù)名大概可以推測: 將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);
-
-
- } else {
-
- it.remove();
- }
- }
-
- return changes != 0;
- }
Resources.updateSystemConfiguration()清除一部分系統(tǒng)資源, 并且將config更新到Resources, 而Resources包含了一個AssetManager對象, 該對象的核心實現(xiàn)是在AssetManager.cpp中完成的. 然后循環(huán)清空mActivityResources資源. 再回到handleConfigurationChanged()函數(shù), 執(zhí)行完updateSystemConfiguration后, 會循環(huán)該進程的所有activity:
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i<N; i++) {
performConfigurationChanged(callbacks.get(i), config);
}
}
再來看performConfigurationChanged的實現(xiàn):
- private final void performConfigurationChanged(
- ComponentCallbacks2 cb, Configuration config) {
-
-
-
- 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 {
-
-
-
-
- int diff = activity.mCurrentConfig.diff(config);
- if (diff != 0) {
-
-
-
- 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);
- }
- }
- }
該函數(shù)判斷configuration是否改變, 如果改變那么shouldChangeConfig為true. 然后調(diào)用activity的onConfigurationChange(config);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public void onConfigurationChanged(Configuration newConfig) {
- mCalled = true;
-
- mFragments.dispatchConfigurationChanged(newConfig);
-
- if (mWindow != null) {
-
- mWindow.onConfigurationChanged(newConfig);
- }
-
- if (mActionBar != null) {
-
-
- mActionBar.onConfigurationChanged(newConfig);
- }
- }
查看注釋, 大概意思是: 如果你的activity運行 , 設備信息有改變(即configuration改變)時由系統(tǒng)調(diào)用. 如果你在manifest.xml中配置了configChnages屬性則表示有你自己來處理configuration change. 否則就重啟當前這個activity. 而重啟之前, 舊的resources已經(jīng)被清空, 那么就會裝載新的資源, 整個過程就完成了語言切換后 , 能夠讓所有app使用新的語言. 語言切換流程大概分為三步:
第一步: 判斷configuration的local是否已經(jīng)改變, 如果改變則將local更新到當前的configuration
第二步: 清空舊的資源.
第三步: 重啟所有所有進程并加裝新資源.
由于個人知識水平有限, 有些地方不免有些紕漏, 希望大牛多多指點. 也希望有共同興趣愛好的人進行技術交流.