Android手機語言切換行為,是通過設置-語言和輸入法-語言來改變手機的語言,其實這個功能很少被用戶使用。
以Android5.1工程源碼為基礎,從設置app入手來分析和學習語言切換的過程:
一、語言設置界面:
首先在設置app中找到語言設置這個Preference,目前設置中界面大多都是Fragment,先找到語言和輸入法的PreferenceScreen,與其對應的Fragment是InputMethodAndLanguageSettings.java,在其onCreate()方法中,首先是增加語言設置的preference:? ??
- addPreferencesFromResource(R.xml.language_settings);??
找到language_settings.xml,可發現如下代碼:? ?
- <PreferenceScreen??
- ????????????android:key="phone_language"??
- ????????????android:title="@string/phone_language"??
- ????????????android:fragment="com.android.settings.LocalePicker"??
- ????????????/>??
于是斷定LocalePicker就是語言設置的Fragment,它是ListFragment的子類,繼承于framework中LocalePicker,并實現了父類的一個接口,其回調方法是onLocaleSelected(),Locale中文含義大致是語言環境,所以可推測這是設置語言后的一個回調方法,不確定的話,可打斷點測試一下。然而此類中并沒有關于語言設置界面數據適配的太多邏輯,只是通過父類的方法創建了一個view:? ?
- @Override??
- ???public?View?onCreateView(??
- ???????????LayoutInflater?inflater,?ViewGroup?container,?Bundle?savedInstanceState)?{??
- ???????final?View?view?=?super.onCreateView(inflater,?container,?savedInstanceState);??
- ???????final?ListView?list?=?(ListView)?view.findViewById(android.R.id.list);??
- ???????Utils.forcePrepareCustomPreferencesList(container,?view,?list,?false);??
- ???????return?view;??
- ???}??
所以更多邏輯應該在framework中的LocalePicker.java中。既然是ListFragment,那就必須有Adapter,在此類中有構建了一個Adapter:
- /**?
- ????*?Constructs?an?Adapter?object?containing?Locale?information.?Content?is?sorted?by?
- ????*?{@link?LocaleInfo#label}.?
- ????*/??
- ???public?static?ArrayAdapter<LocaleInfo>?constructAdapter(Context?context)?{??
- ???????return?constructAdapter(context,?R.layout.locale_picker_item,?R.id.locale);??
- ???}??
- ???public?static?ArrayAdapter<LocaleInfo>?constructAdapter(Context?context,??
- ???????????final?int?layoutId,?final?int?fieldId)?{??
- ???????boolean?isInDeveloperMode?=?Settings.Global.getInt(context.getContentResolver(),??
- ???????????????Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,?0)?!=?0;??
- ???????//獲取系統支持語言的信息??
- ???????final?List<LocaleInfo>?localeInfos?=?getAllAssetLocales(context,?isInDeveloperMode);??
- ???????final?LayoutInflater?inflater?=??
- ???????????????(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);??
- ???????return?new?ArrayAdapter<LocaleInfo>(context,?layoutId,?fieldId,?localeInfos)?{??
- ???????????@Override??
- ???????????public?View?getView(int?position,?View?convertView,?ViewGroup?parent)?{??
- ???????????????View?view;??
- ???????????????TextView?text;??
- ???????????????if?(convertView?==?null)?{??
- ???????????????????view?=?inflater.inflate(layoutId,?parent,?false);??
- ???????????????????text?=?(TextView)?view.findViewById(fieldId);??
- ???????????????????view.setTag(text);??
- ???????????????}?else?{??
- ???????????????????view?=?convertView;??
- ???????????????????text?=?(TextView)?view.getTag();??
- ???????????????}??
- ???????????????LocaleInfo?item?=?getItem(position);??
- ???????????????text.setText(item.toString());??
- ???????????????text.setTextLocale(item.getLocale());??
- ???????????????return?view;??
- ???????????}??
- ???????};??
- ???}??
- public?static?List<LocaleInfo>?getAllAssetLocales(Context?context,?boolean?isInDeveloperMode)?{??
- ????????final?Resources?resources?=?context.getResources();??
- ????????//獲取系統所支持的語言??
- ????????final?String[]?locales?=?Resources.getSystem().getAssets().getLocales();??
- ????????List<String>?localeList?=?new?ArrayList<String>(locales.length);??
- ????????Collections.addAll(localeList,?locales);??
- ??
- ????????//?Don't?show?the?pseudolocales?unless?we're?in?developer?mode.???
- ????????if?(!isInDeveloperMode)?{??
- ????????????localeList.remove("ar-XB");??
- ????????????localeList.remove("en-XA");??
- ????????}??
- ??
- ????????Collections.sort(localeList);??
- ????????final?String[]?specialLocaleCodes?=?resources.getStringArray(R.array.special_locale_codes);??
- ????????final?String[]?specialLocaleNames?=?resources.getStringArray(R.array.special_locale_names);??
- ??
- ????????final?ArrayList<LocaleInfo>?localeInfos?=?new?ArrayList<LocaleInfo>(localeList.size());??
- ????????for?(String?locale?:?localeList)?{??
- ????????????final?Locale?l?=?Locale.forLanguageTag(locale.replace('_',?'-'));??
- ????????????if?(l?==?null?||?"und".equals(l.getLanguage())??
- ????????????????????||?l.getLanguage().isEmpty()?||?l.getCountry().isEmpty())?{??
- ????????????????continue;??
- ????????????}??
- ??
- ????????????if?(localeInfos.isEmpty())?{??
- ????????????????if?(DEBUG)?{??
- ????????????????????Log.v(TAG,?"adding?initial?"+?toTitleCase(l.getDisplayLanguage(l)));??
- ????????????????}??
- ????????????????localeInfos.add(new?LocaleInfo(toTitleCase(l.getDisplayLanguage(l)),?l));??
- ????????????}?else?{??
- ????????????????//?check?previous?entry:??
- ????????????????//??same?lang?and?a?country?->?upgrade?to?full?name?and??
- ????????????????//????insert?ours?with?full?name??
- ????????????????//??diff?lang?->?insert?ours?with?lang-only?name??
- ????????????????final?LocaleInfo?previous?=?localeInfos.get(localeInfos.size()?-?1);??
- ????????????????if?(previous.locale.getLanguage().equals(l.getLanguage())?&&??
- ????????????????????????!previous.locale.getLanguage().equals("zz"))?{??
- ????????????????????if?(DEBUG)?{??
- ????????????????????????Log.v(TAG,?"backing?up?and?fixing?"?+?previous.label?+?"?to?"?+??
- ????????????????????????????????getDisplayName(previous.locale,?specialLocaleCodes,?specialLocaleNames));??
- ????????????????????}??
- ????????????????????previous.label?=?toTitleCase(getDisplayName(??
- ????????????????????????????previous.locale,?specialLocaleCodes,?specialLocaleNames));??
- ????????????????????if?(DEBUG)?{??
- ????????????????????????Log.v(TAG,?"??and?adding?"+?toTitleCase(??
- ????????????????????????????????getDisplayName(l,?specialLocaleCodes,?specialLocaleNames)));??
- ????????????????????}??
- ????????????????????localeInfos.add(new?LocaleInfo(toTitleCase(??
- ????????????????????????????getDisplayName(l,?specialLocaleCodes,?specialLocaleNames)),?l));??
- ????????????????}?else?{??
- ????????????????????String?displayName?=?toTitleCase(l.getDisplayLanguage(l));??
- ????????????????????if?(DEBUG)?{??
- ????????????????????????Log.v(TAG,?"adding?"+displayName);??
- ????????????????????}??
- ????????????????????localeInfos.add(new?LocaleInfo(displayName,?l));??
- ????????????????}??
- ????????????}??
- ????????}??
- ??
- ????????Collections.sort(localeInfos);??
- ????????return?localeInfos;??
- ????}??
此方法中還會通過Resources.getSystem().getAssets().getLocales()去獲得系統支持的語言信息,然后添加LocaleInfo里邊,再通過Adapter適配到ListView中。getLocales()方法屬于類AssetManager.java:
- /**?
- ?????*?Get?the?locales?that?this?asset?manager?contains?data?for.?
- ?????*?
- ?????*?<p>On?SDK?21?(Android?5.0:?Lollipop)?and?above,?Locale?strings?are?valid?
- ?????*?<a?href="https://tools.ietf.org/html/bcp47">BCP-47</a>?language?tags?and?can?be?
- ?????*?parsed?using?{@link?java.util.Locale#forLanguageTag(String)}.?
- ?????*?
- ?????*?<p>On?SDK?20?(Android?4.4W:?Kitkat?for?watches)?and?below,?locale?strings?
- ?????*?are?of?the?form?{@code?ll_CC}?where?{@code?ll}?is?a?two?letter?language?code,?
- ?????*?and?{@code?CC}?is?a?two?letter?country?code.?
- ?????*/??
- ????public?native?final?String[]?getLocales();??
乍一看,是個native方法,那不就是跟JNI有關系了,所以只能到相應JNI目錄下去找了,路徑:android5.1\frameworks\base\core\jni,對應文件:android_util_AssetManager.cpp(瀏覽下這個文件,發現這個家伙有點不得了啊,什么resource,theme等都跟它有關系,看樣子還的加油學學JNI啊!),然后找到對應的native方法:
- static?jobjectArray?android_content_AssetManager_getLocales(JNIEnv*?env,?jobject?clazz)??
- {??
- ????Vector<String8>?locales;??
- ??
- ????AssetManager*?am?=?assetManagerForJavaObject(env,?clazz);??
- ????if?(am?==?NULL)?{??
- ????????return?NULL;??
- ????}??
- ??
- ????am->getLocales(&locales);??
- ??
- ????const?int?N?=?locales.size();??
- ??
- ????jobjectArray?result?=?env->NewObjectArray(N,?g_stringClass,?NULL);??
- ????if?(result?==?NULL)?{??
- ????????return?NULL;??
- ????}??
- ??
- ????for?(int?i=0;?i<N;?i++)?{??
- ????????jstring?str?=?env->NewStringUTF(locales[i].string());??
- ????????if?(str?==?NULL)?{??
- ????????????return?NULL;??
- ????????}??
- ????????env->SetObjectArrayElement(result,?i,?str);??
- ????????env->DeleteLocalRef(str);??
- ????}??
- ??
- ????return?result;??
- }??
二、語言設置功能實現過程:
上面提到了設置中的LocalePicker類實現了父類接口中的onLocaleSelected()方法:? ?
- public?static?interface?LocaleSelectionListener?{??
- ???????//?You?can?add?any?argument?if?you?really?need?it...??
- ???????public?void?onLocaleSelected(Locale?locale);??
- ???}??????
- ?????
- ???@Override??
- ???public?void?onLocaleSelected(final?Locale?locale)?{??
- ???????if?(Utils.hasMultipleUsers(getActivity()))?{??
- ???????????mTargetLocale?=?locale;??
- ???????????showDialog(DLG_SHOW_GLOBAL_WARNING);??
- ???????}?else?{??
- ???????????getActivity().onBackPressed();??
- ???????????LocalePicker.updateLocale(locale);??
- ???????}??
- ???}??
此方法中最終調用了其父類的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();??
- ?????????//?Will?set?userSetLocale?to?indicate?this?isn't?some?passing?default?-?the?user??
- ?????????//?wants?this?remembered??
- ?????????config.setLocale(locale);??
- ?????????am.updateConfiguration(config);??
- ?????????//?Trigger?the?dirty?bit?for?the?Settings?Provider.??
- ?????????BackupManager.dataChanged("com.android.providers.settings");??
- ?????}?catch?(RemoteException?e)?{??
- ?????????//?Intentionally?left?blank??
- ?????}??
- ?}??
又看到ActivityManagerNative.getDefault(),所以可以直接到ActivityManagerService.java中找對應的方法,此方法中先是把選擇的語言設置到Configuration中,記錄下來。設置了不代表系統就知道這檔子事,所以還需要am去更新一下,說的俗氣一點:am老大知道了這檔子事,然后大吼一聲,我這里有個東西改變了,小伙伴們刷新一下!在ActivityManagerService中找到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);??
- ????????}??
- ????}??
看到Settings.System.clearConfiguration(values)不要以為這里把values清除了額,其實這個方法只是把系統字體的特效清除了,比如字體的大小:? ?
- /**?
- ?????????*?@hide?Erase?the?fields?in?the?Configuration?that?should?be?applied?
- ?????????*?by?the?settings.?
- ?????????*/??
- ????????public?static?void?clearConfiguration(Configuration?inoutConfig)?{??
- ????????????inoutConfig.fontScale?=?0;??
- ????????}??
然后調用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?
- ?????*/??
- boolean?updateConfigurationLocked(Configuration?values,??
- ????????????ActivityRecord?starting,?boolean?persistent,?boolean?initLocale)?{??
- ????????int?changes?=?0;??
- ????????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);??
- ????????????????}??
- ????????????????mConfigurationSeq++;??
- ????????????????if?(mConfigurationSeq?<=?0)?{??
- ????????????????????mConfigurationSeq?=?1;??
- ????????????????}??
- ????????????????newConfig.seq?=?mConfigurationSeq;??
- ????????????????mConfiguration?=?newConfig;??
- ????????????????Slog.i(TAG,?"Config?changes="?+?Integer.toHexString(changes)?+?"?"?+?newConfig);??
- ????????????????mUsageStatsService.reportConfigurationChange(newConfig,?mCurrentUserId);??
- ????????????????//mUsageStatsService.noteStartConfig(newConfig);??
- ????????????????final?Configuration?configCopy?=?new?Configuration(mConfiguration);??
- ?????????????????
- ????????????????//?TODO:?If?our?config?changes,?should?we?auto?dismiss?any?currently??
- ????????????????//?showing?dialogs???
- ????????????????mShowDialogs?=?shouldShowDialogs(newConfig);??
- ????????????????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??
- ????????????????????????|?Intent.FLAG_RECEIVER_FOREGROUND);??
- ????????????????broadcastIntentLocked(null,?null,?intent,?null,?null,?0,?null,?null,??
- ????????????????????????null,?AppOpsManager.OP_NONE,?false,?false,?MY_PID,??
- ????????????????????????Process.SYSTEM_UID,?UserHandle.USER_ALL);??
- ????????????????if?((changes&ActivityInfo.CONFIG_LOCALE)?!=?0)?{??
- ????????????????????intent?=?new?Intent(Intent.ACTION_LOCALE_CHANGED);??
- ????????????????????intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);??
- ????????????????????broadcastIntentLocked(null,?null,?intent,??
- ????????????????????????????null,?null,?0,?null,?null,?null,?AppOpsManager.OP_NONE,??
- ????????????????????????????false,?false,?MY_PID,?Process.SYSTEM_UID,?UserHandle.USER_ALL);??
- ????????????????}??
- ????????????}??
- ????????}??
- ????????boolean?kept?=?true;??
- ????????final?ActivityStack?mainStack?=?mStackSupervisor.getFocusedStack();??
- ????????//?mainStack?is?null?during?startup.??
- ????????if?(mainStack?!=?null)?{??
- ????????????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?=?mainStack.topRunningActivityLocked(null);??
- ????????????}??
- ????????????if?(starting?!=?null)?{??
- ????????????????kept?=?mainStack.ensureActivityConfigurationLocked(starting,?changes);??
- ????????????????//?And?we?need?to?make?sure?at?this?point?that?all?other?activities??
- ????????????????//?are?made?visible?with?the?correct?configuration.??
- ????????????????mStackSupervisor.ensureActivitiesVisibleLocked(starting,?changes);??
- ????????????}??
- ????????}??
- ????????if?(values?!=?null?&&?mWindowManager?!=?null)?{??
- ????????????mWindowManager.setNewConfiguration(mConfiguration);??
- ????????}??
- ????????return?kept;??
- ????}??
有了新的數據就要保存,保存在configuration中不是個事。對于Android系統而言,改變語言,有兩個地方的數據需要更新,一個是SystemProperties,另一個是數據庫。前者以鍵值對的形式存放數據,多用于System,后者保存于DataBase中,多用于應用程序獲取,算是對外開放的數據。上面方法中對這兩個地方都進行了數據保存操作:
1)SystemProperties:調用saveLocaleLocked()方法:
- /**?
- ?????*?Save?the?locale.??You?must?be?inside?a?synchronized?(this)?block.?
- ?????*/??
- ????private?void?saveLocaleLocked(Locale?l,?boolean?isDiff,?boolean?isPersist)?{??
- ????????if(isDiff)?{??
- ????????????SystemProperties.set("user.language",?l.getLanguage());??
- ????????????SystemProperties.set("user.region",?l.getCountry());??
- ????????}??
- ????????if(isPersist)?{??
- ????????????SystemProperties.set("persist.sys.language",?l.getLanguage());??
- ????????????SystemProperties.set("persist.sys.country",?l.getCountry());??
- ????????????SystemProperties.set("persist.sys.localevar",?l.getVariant());??
- ????????????mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,?l));??
- ????????}??
- ????}??
2)database:調用Settings.System.putConfiguration()方法:
- if?(persistent?&&?Settings.System.hasInterestingConfigurationChanges(changes))?{??
- ???????????????????Message?msg?=?mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);??
- ???????????????????msg.obj?=?new?Configuration(configCopy);??
- ???????????????????mHandler.sendMessage(msg);??
- ???}??
- ???...??
- ???case?UPDATE_CONFIGURATION_MSG:?{??
- ???????????????final?ContentResolver?resolver?=?mContext.getContentResolver();??
- ???????????????Settings.System.putConfiguration(resolver,?(Configuration)msg.obj);??
- ???????????}?break;??
該保存的數據保存了,但是Resource還不知道這檔子事,因為Android代碼和資源是分開的,Resource不知道Configuration發生了變化,Resource就不會去加載正確的資源。所以接下來此方法調用了mSystemThread.applyConfigurationToResources(configCopy)來完成這件事,mSystemThread是一個ActivityThread對象,其初始化在ActivityManagerService的構造函數中完成:? ?
- mSystemThread?=?ActivityThread.currentActivityThread();??
- //此方法屬于ActivityThread??
- ????public?final?void?applyConfigurationToResources(Configuration?config)?{??
- ????????synchronized?(mResourcesManager)?{??
- ????????????mResourcesManager.applyConfigurationToResourcesLocked(config,?null);??
- ????????}??
- ????}??
- ????//此方法屬于ResourcesManage??
- ????public?final?boolean?applyConfigurationToResourcesLocked(Configuration?config,??
- ????????????CompatibilityInfo?compat)?{??
- ????????if?(mResConfiguration?==?null)?{??
- ????????????mResConfiguration?=?new?Configuration();??
- ????????}??
- ????????if?(!mResConfiguration.isOtherSeqNewer(config)?&&?compat?==?null)?{??
- ????????????if?(DEBUG_CONFIGURATION)?Slog.v(TAG,?"Skipping?new?config:?curSeq="??
- ????????????????????+?mResConfiguration.seq?+?",?newSeq="?+?config.seq);??
- ????????????return?false;??
- ????????}??
- ????????int?changes?=?mResConfiguration.updateFrom(config);??
- ????????flushDisplayMetricsLocked();??
- ????????DisplayMetrics?defaultDisplayMetrics?=?getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);??
- ????????if?(compat?!=?null?&&?(mResCompatibilityInfo?==?null?||??
- ????????????????!mResCompatibilityInfo.equals(compat)))?{??
- ????????????mResCompatibilityInfo?=?compat;??
- ????????????changes?|=?ActivityInfo.CONFIG_SCREEN_LAYOUT??
- ????????????????????|?ActivityInfo.CONFIG_SCREEN_SIZE??
- ????????????????????|?ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;??
- ????????}??
- ????????//?set?it?for?java,?this?also?affects?newly?created?Resources??
- ????????if?(config.locale?!=?null)?{??
- ????????????Locale.setDefault(config.locale);??
- ????????}??
- ????????Resources.updateSystemConfiguration(config,?defaultDisplayMetrics,?compat);??
- ????????ApplicationPackageManager.configurationChanged();??
- ????????//Slog.i(TAG,?"Configuration?changed?in?"?+?currentPackageName());??
- ????????Configuration?tmpConfig?=?null;??
- ????????for?(int?i=mActiveResources.size()-1;?i>=0;?i--)?{??
- ????????????ResourcesKey?key?=?mActiveResources.keyAt(i);??
- ????????????Resources?r?=?mActiveResources.valueAt(i).get();??
- ????????????if?(r?!=?null)?{??
- ????????????????if?(DEBUG_CONFIGURATION)?Slog.v(TAG,?"Changing?resources?"??
- ????????????????????????+?r?+?"?config?to:?"?+?config);??
- ????????????????int?displayId?=?key.mDisplayId;??
- ????????????????boolean?isDefaultDisplay?=?(displayId?==?Display.DEFAULT_DISPLAY);??
- ????????????????DisplayMetrics?dm?=?defaultDisplayMetrics;??
- ????????????????final?boolean?hasOverrideConfiguration?=?key.hasOverrideConfiguration();??
- ????????????????if?(!isDefaultDisplay?||?hasOverrideConfiguration)?{??
- ????????????????????if?(tmpConfig?==?null)?{??
- ????????????????????????tmpConfig?=?new?Configuration();??
- ????????????????????}??
- ????????????????????tmpConfig.setTo(config);??
- ????????????????????if?(!isDefaultDisplay)?{??
- ????????????????????????dm?=?getDisplayMetricsLocked(displayId);??
- ????????????????????????applyNonDefaultDisplayMetricsToConfigurationLocked(dm,?tmpConfig);??
- ????????????????????}??
- ????????????????????if?(hasOverrideConfiguration)?{??
- ????????????????????????tmpConfig.updateFrom(key.mOverrideConfiguration);??
- ????????????????????}??
- ????????????????????r.updateConfiguration(tmpConfig,?dm,?compat);??
- ????????????????}?else?{??
- ????????????????????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());??
- ????????????????mActiveResources.removeAt(i);??
- ????????????}??
- ????????}??
- ????????return?changes?!=?0;??
- ????}??
此方法中Resource和ApplicationPackageManager都會去更新configuration,configuration所包含的屬性都會遍歷到,該更新的數據更新,該清除的緩存清除。
到這里,第一件事算是做完了,就要做第二件事,讓新的configuration更新到所有界面,updateConfigurationLocked()方法通過遍歷保存在ProcessRecord中的進程,然后通過scheduleConfigurationChanged()方法更新它們的configuration:
- 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)?{??
- ?????}??
- ?}??
此處通過Binder機制調用ApplicationThreadNative.java中的scheduleConfigurationChanged()方法,最后調用到ActivityThread中的內部類ApplicationThread的scheduleConfigurationChanged()方法,函數調用堆棧如圖:
- public?void?scheduleConfigurationChanged(Configuration?config)?{??
- ????????????updatePendingConfiguration(config);??
- ????????????sendMessage(H.CONFIGURATION_CHANGED,?config);??
- ????}??
- case?CONFIGURATION_CHANGED:??
- ????????????????????Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,?"configChanged");??
- ????????????????????mCurDefaultDisplayDpi?=?((Configuration)msg.obj).densityDpi;??
- ????????????????????handleConfigurationChanged((Configuration)msg.obj,?null);??
- ????????????????????Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);??
- ????????????????????break;??
- final?void?handleConfigurationChanged(Configuration?config,?CompatibilityInfo?compat)?{??
- ????????int?configDiff?=?0;??
- ????????synchronized?(mResourcesManager)?{??
- ????????????if?(mPendingConfiguration?!=?null)?{??
- ????????????????if?(!mPendingConfiguration.isOtherSeqNewer(config))?{??
- ????????????????????config?=?mPendingConfiguration;??
- ????????????????????mCurDefaultDisplayDpi?=?config.densityDpi;??
- ????????????????????updateDefaultDensity();??
- ????????????????}??
- ????????????????mPendingConfiguration?=?null;??
- ????????????}??
- ????????????if?(config?==?null)?{??
- ????????????????return;??
- ????????????}??
- ?????????????
- ????????????if?(DEBUG_CONFIGURATION)?Slog.v(TAG,?"Handle?configuration?changed:?"??
- ????????????????????+?config);??
- ????????????mResourcesManager.applyConfigurationToResourcesLocked(config,?compat);??
- ????????????if?(mConfiguration?==?null)?{??
- ????????????????mConfiguration?=?new?Configuration();??
- ????????????}??
- ????????????if?(!mConfiguration.isOtherSeqNewer(config)?&&?compat?==?null)?{??
- ????????????????return;??
- ????????????}??
- ????????????configDiff?=?mConfiguration.diff(config);??
- ????????????mConfiguration.updateFrom(config);??
- ????????????config?=?applyCompatConfiguration(mCurDefaultDisplayDpi);??
- ????????}??
- ????????ArrayList<ComponentCallbacks2>?callbacks?=?collectComponentCallbacks(false,?config);??
- ????????freeTextLayoutCachesIfNeeded(configDiff);??
- ????????if?(callbacks?!=?null)?{??
- ????????????final?int?N?=?callbacks.size();??
- ????????????for?(int?i=0;?i<N;?i++)?{??
- ????????????????performConfigurationChanged(callbacks.get(i),?config);??
- ????????????}??
- ????????}??
- ????}??
到這里設置語言以后,代碼跑的流程就基本結束了,需要一提的是performConfigurationChanged()方法。為什么要提它呢?因為有時候寫應用的時候activity需要關注一些configChanged,如:android:configChanges="orientation|keyboardHidden|screenSize",然后重寫onConfigurationChanged()方法。然而觸發這個方法回調的觸發點在哪里呢?這里就以設置語言為例,設置語言觸發了configuration的改變。先來看下performConfigurationChanged()方法:? ??
- private?static?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確實改變了,那么此方法中就會調用cb.onConfigurationChanged(config)。cb代表ComponentCallbacks2,而ComponentCallbacks2 又繼承于ComponentCallbacks,所以onConfigurationChanged()方法屬于ComponentCallbacks,同樣Activity類也實現了ComponentCallbacks2這個接口,如此一來這個回調的過程就連接上了。也充分說明了為什么在configuration改變以后,activity關注的config會回調其父類的onConfigurationChanged()方法。
最后就是廣播configuration改變了,updateConfigurationLocked()廣播了ACTION_CONFIGURATION_CHANGED和ACTION_LOCALE_CHANGED,使用的方法是broadcastIntentLocked(),此方法廣播成功返回BROADCAST_SUCCESS。具體就不多說了。