Android-语言设置流程分析

Android手机语言切换行为,是通过设置-语言和输入法-语言来改变手机的语言,其实这个功能很少被用户使用。

    以Android5.1工程源码为基础,从设置app入手来分析和学习语言切换的过程:
    一、语言设置界面:
    首先在设置app中找到语言设置这个Preference,目前设置中界面大多都是Fragment,先找到语言和输入法的PreferenceScreen,与其对应的Fragment是InputMethodAndLanguageSettings.java,在其onCreate()方法中,首先是增加语言设置的preference:    
  1. addPreferencesFromResource(R.xml.language_settings);
找到language_settings.xml,可发现如下代码:   
  1. <PreferenceScreen
  2. android:key="phone_language"
  3. android:title="@string/phone_language"
  4. android:fragment="com.android.settings.LocalePicker"
  5. />
于是断定LocalePicker就是语言设置的Fragment,它是ListFragment的子类,继承于framework中LocalePicker,并实现了父类的一个接口,其回调方法是onLocaleSelected(),Locale中文含义大致是语言环境,所以可推测这是设置语言后的一个回调方法,不确定的话,可打断点测试一下。然而此类中并没有关于语言设置界面数据适配的太多逻辑,只是通过父类的方法创建了一个view:   
  1. @Override
  2. public View onCreateView(
  3. LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  4. final View view = super.onCreateView(inflater, container, savedInstanceState);
  5. final ListView list = (ListView) view.findViewById(android.R.id.list);
  6. Utils.forcePrepareCustomPreferencesList(container, view, list, false);
  7. return view;
  8. }
   所以更多逻辑应该在framework中的LocalePicker.java中。既然是ListFragment,那就必须有Adapter,在此类中有构建了一个Adapter:
  1. /**
  2. * Constructs an Adapter object containing Locale information. Content is sorted by
  3. * {@link LocaleInfo#label}.
  4. */
  5. public static ArrayAdapter<LocaleInfo> constructAdapter(Context context) {
  6. return constructAdapter(context, R.layout.locale_picker_item, R.id.locale);
  7. }
  8. public static ArrayAdapter<LocaleInfo> constructAdapter(Context context,
  9. final int layoutId, final int fieldId) {
  10. boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
  11. Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
  12. //获取系统支持语言的信息
  13. final List<LocaleInfo> localeInfos = getAllAssetLocales(context, isInDeveloperMode);
  14. final LayoutInflater inflater =
  15. (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  16. return new ArrayAdapter<LocaleInfo>(context, layoutId, fieldId, localeInfos) {
  17. @Override
  18. public View getView(int position, View convertView, ViewGroup parent) {
  19. View view;
  20. TextView text;
  21. if (convertView == null) {
  22. view = inflater.inflate(layoutId, parent, false);
  23. text = (TextView) view.findViewById(fieldId);
  24. view.setTag(text);
  25. } else {
  26. view = convertView;
  27. text = (TextView) view.getTag();
  28. }
  29. LocaleInfo item = getItem(position);
  30. text.setText(item.toString());
  31. text.setTextLocale(item.getLocale());
  32. return view;
  33. }
  34. };
  35. }

而此方法通过getAllAssetLocales()方法获取系统支持语言的信息:

  1. public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) {
  2. final Resources resources = context.getResources();
  3. //获取系统所支持的语言
  4. final String[] locales = Resources.getSystem().getAssets().getLocales();
  5. List<String> localeList = new ArrayList<String>(locales.length);
  6. Collections.addAll(localeList, locales);
  7. // Don't show the pseudolocales unless we're in developer mode.
  8. if (!isInDeveloperMode) {
  9. localeList.remove("ar-XB");
  10. localeList.remove("en-XA");
  11. }
  12. Collections.sort(localeList);
  13. final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
  14. final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
  15. final ArrayList<LocaleInfo> localeInfos = new ArrayList<LocaleInfo>(localeList.size());
  16. for (String locale : localeList) {
  17. final Locale l = Locale.forLanguageTag(locale.replace('_', '-'));
  18. if (l == null || "und".equals(l.getLanguage())
  19. || l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
  20. continue;
  21. }
  22. if (localeInfos.isEmpty()) {
  23. if (DEBUG) {
  24. Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l)));
  25. }
  26. localeInfos.add(new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l));
  27. } else {
  28. // check previous entry:
  29. //  same lang and a country -> upgrade to full name and
  30. //    insert ours with full name
  31. //  diff lang -> insert ours with lang-only name
  32. final LocaleInfo previous = localeInfos.get(localeInfos.size() - 1);
  33. if (previous.locale.getLanguage().equals(l.getLanguage()) &&
  34. !previous.locale.getLanguage().equals("zz")) {
  35. if (DEBUG) {
  36. Log.v(TAG, "backing up and fixing " + previous.label + " to " +
  37. getDisplayName(previous.locale, specialLocaleCodes, specialLocaleNames));
  38. }
  39. previous.label = toTitleCase(getDisplayName(
  40. previous.locale, specialLocaleCodes, specialLocaleNames));
  41. if (DEBUG) {
  42. Log.v(TAG, "  and adding "+ toTitleCase(
  43. getDisplayName(l, specialLocaleCodes, specialLocaleNames)));
  44. }
  45. localeInfos.add(new LocaleInfo(toTitleCase(
  46. getDisplayName(l, specialLocaleCodes, specialLocaleNames)), l));
  47. } else {
  48. String displayName = toTitleCase(l.getDisplayLanguage(l));
  49. if (DEBUG) {
  50. Log.v(TAG, "adding "+displayName);
  51. }
  52. localeInfos.add(new LocaleInfo(displayName, l));
  53. }
  54. }
  55. }
  56. Collections.sort(localeInfos);
  57. return localeInfos;
  58. }
    此方法中还会通过Resources.getSystem().getAssets().getLocales()去获得系统支持的语言信息,然后添加LocaleInfo里边,再通过Adapter适配到ListView中。getLocales()方法属于类AssetManager.java:
  1. /**
  2. * Get the locales that this asset manager contains data for.
  3. *
  4. * <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid
  5. * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be
  6. * parsed using {@link java.util.Locale#forLanguageTag(String)}.
  7. *
  8. * <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings
  9. * are of the form {@code ll_CC} where {@code ll} is a two letter language code,
  10. * and {@code CC} is a two letter country code.
  11. */
  12. public native final String[] getLocales();
乍一看,是个native方法,那不就是跟JNI有关系了,所以只能到相应JNI目录下去找了,路径:android5.1\frameworks\base\core\jni,对应文件:android_util_AssetManager.cpp(浏览下这个文件,发现这个家伙有点不得了啊,什么resource,theme等都跟它有关系,看样子还的加油学学JNI啊!),然后找到对应的native方法:
  1. static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz)
  2. {
  3. Vector<String8> locales;
  4. AssetManager* am = assetManagerForJavaObject(env, clazz);
  5. if (am == NULL) {
  6. return NULL;
  7. }
  8. am->getLocales(&locales);
  9. const int N = locales.size();
  10. jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL);
  11. if (result == NULL) {
  12. return NULL;
  13. }
  14. for (int i=0; i<N; i++) {
  15. jstring str = env->NewStringUTF(locales[i].string());
  16. if (str == NULL) {
  17. return NULL;
  18. }
  19. env->SetObjectArrayElement(result, i, str);
  20. env->DeleteLocalRef(str);
  21. }
  22. return result;
  23. }

通过上面初步的分析,语言的List界面就基本出来了,在getAllAssetLocales()方法中打了个断点,查看了下locales被赋值以后的值:

Android-语言设置流程分析
Android-语言设置流程分析
二、语言设置功能实现过程:
上面提到了设置中的LocalePicker类实现了父类接口中的onLocaleSelected()方法:
  1. public static interface LocaleSelectionListener {
  2. // You can add any argument if you really need it...
  3. public void onLocaleSelected(Locale locale);
  4. }
  5. @Override
  6. public void onLocaleSelected(final Locale locale) {
  7. if (Utils.hasMultipleUsers(getActivity())) {
  8. mTargetLocale = locale;
  9. showDialog(DLG_SHOW_GLOBAL_WARNING);
  10. } else {
  11. getActivity().onBackPressed();
  12. LocalePicker.updateLocale(locale);
  13. }
  14. }
此方法中最终调用了其父类的updateLocale()方法来更新系统的语言环境:  
  1. /**
  2. * Requests the system to update the system locale. Note that the system looks halted
  3. * for a while during the Locale migration, so the caller need to take care of it.
  4. */
  5. public static void updateLocale(Locale locale) {
  6. try {
  7. IActivityManager am = ActivityManagerNative.getDefault();
  8. Configuration config = am.getConfiguration();
  9. // Will set userSetLocale to indicate this isn't some passing default - the user
  10. // wants this remembered
  11. config.setLocale(locale);
  12. am.updateConfiguration(config);
  13. // Trigger the dirty bit for the Settings Provider.
  14. BackupManager.dataChanged("com.android.providers.settings");
  15. } catch (RemoteException e) {
  16. // Intentionally left blank
  17. }
  18. }
  又看到ActivityManagerNative.getDefault(),所以可以直接到ActivityManagerService.java中找对应的方法,此方法中先是把选择的语言设置到Configuration中,记录下来。设置了不代表系统就知道这档子事,所以还需要am去更新一下,说的俗气一点:am老大知道了这档子事,然后大吼一声,我这里有个东西改变了,小伙伴们刷新一下!在ActivityManagerService中找到updateConfiguration()方法:
  1. public void updateConfiguration(Configuration values) {
  2. enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
  3. "updateConfiguration()");
  4. synchronized(this) {
  5. if (values == null && mWindowManager != null) {
  6. // sentinel: fetch the current configuration from the window manager
  7. values = mWindowManager.computeNewConfiguration();
  8. }
  9. if (mWindowManager != null) {
  10. mProcessList.applyDisplaySize(mWindowManager);
  11. }
  12. final long origId = Binder.clearCallingIdentity();
  13. if (values != null) {
  14. Settings.System.clearConfiguration(values);
  15. }
  16. updateConfigurationLocked(values, null, false, false);
  17. Binder.restoreCallingIdentity(origId);
  18. }
  19. }
看到Settings.System.clearConfiguration(values)不要以为这里把values清除了额,其实这个方法只是把系统字体的特效清除了,比如字体的大小:   
  1. /**
  2. * @hide Erase the fields in the Configuration that should be applied
  3. * by the settings.
  4. */
  5. public static void clearConfiguration(Configuration inoutConfig) {
  6. inoutConfig.fontScale = 0;
  7. }
然后调用updateConfigurationLocked()方法:
  1. /**
  2. * Do either or both things: (1) change the current configuration, and (2)
  3. * make sure the given activity is running with the (now) current
  4. * configuration.  Returns true if the activity has been left running, or
  5. * false if <var>starting</var> is being destroyed to match the new
  6. * configuration.
  7. * @param persistent TODO
  8. */
  9. boolean updateConfigurationLocked(Configuration values,
  10. ActivityRecord starting, boolean persistent, boolean initLocale) {
  11. int changes = 0;
  12. if (values != null) {
  13. Configuration newConfig = new Configuration(mConfiguration);
  14. changes = newConfig.updateFrom(values);
  15. if (changes != 0) {
  16. if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {
  17. Slog.i(TAG, "Updating configuration to: " + values);
  18. }
  19. EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);
  20. if (values.locale != null && !initLocale) {
  21. saveLocaleLocked(values.locale,
  22. !values.locale.equals(mConfiguration.locale),
  23. values.userSetLocale);
  24. }
  25. mConfigurationSeq++;
  26. if (mConfigurationSeq <= 0) {
  27. mConfigurationSeq = 1;
  28. }
  29. newConfig.seq = mConfigurationSeq;
  30. mConfiguration = newConfig;
  31. Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
  32. mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId);
  33. //mUsageStatsService.noteStartConfig(newConfig);
  34. final Configuration configCopy = new Configuration(mConfiguration);
  35. // TODO: If our config changes, should we auto dismiss any currently
  36. // showing dialogs?
  37. mShowDialogs = shouldShowDialogs(newConfig);
  38. AttributeCache ac = AttributeCache.instance();
  39. if (ac != null) {
  40. ac.updateConfiguration(configCopy);
  41. }
  42. // Make sure all resources in our process are updated
  43. // right now, so that anyone who is going to retrieve
  44. // resource values after we return will be sure to get
  45. // the new ones.  This is especially important during
  46. // boot, where the first config change needs to guarantee
  47. // all resources have that config before following boot
  48. // code is executed.
  49. mSystemThread.applyConfigurationToResources(configCopy);
  50. if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
  51. Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
  52. msg.obj = new Configuration(configCopy);
  53. mHandler.sendMessage(msg);
  54. }
  55. for (int i=mLruProcesses.size()-1; i>=0; i--) {
  56. ProcessRecord app = mLruProcesses.get(i);
  57. try {
  58. if (app.thread != null) {
  59. if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "
  60. + app.processName + " new config " + mConfiguration);
  61. app.thread.scheduleConfigurationChanged(configCopy);
  62. }
  63. } catch (Exception e) {
  64. }
  65. }
  66. Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
  67. intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
  68. | Intent.FLAG_RECEIVER_REPLACE_PENDING
  69. | Intent.FLAG_RECEIVER_FOREGROUND);
  70. broadcastIntentLocked(null, null, intent, null, null, 0, null, null,
  71. null, AppOpsManager.OP_NONE, false, false, MY_PID,
  72. Process.SYSTEM_UID, UserHandle.USER_ALL);
  73. if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {
  74. intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
  75. intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
  76. broadcastIntentLocked(null, null, intent,
  77. null, null, 0, null, null, null, AppOpsManager.OP_NONE,
  78. false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
  79. }
  80. }
  81. }
  82. boolean kept = true;
  83. final ActivityStack mainStack = mStackSupervisor.getFocusedStack();
  84. // mainStack is null during startup.
  85. if (mainStack != null) {
  86. if (changes != 0 && starting == null) {
  87. // If the configuration changed, and the caller is not already
  88. // in the process of starting an activity, then find the top
  89. // activity to check if its configuration needs to change.
  90. starting = mainStack.topRunningActivityLocked(null);
  91. }
  92. if (starting != null) {
  93. kept = mainStack.ensureActivityConfigurationLocked(starting, changes);
  94. // And we need to make sure at this point that all other activities
  95. // are made visible with the correct configuration.
  96. mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes);
  97. }
  98. }
  99. if (values != null && mWindowManager != null) {
  100. mWindowManager.setNewConfiguration(mConfiguration);
  101. }
  102. return kept;
  103. }

此方法主要做两件事:第一,改变当前的configuration,将新的数据放进去。第二,保证正在运行的应用程序界面更新最新的configuration。先调用updateFrom()方法,遍历configuration包含的属性是否改变,如果有改变就返回一个对应的整数,如果没有改变就返回0。就语言改变而言,根据上面的分析,configuration至少有3个属性发生了改变:fontscale(之前没有设置字体的效果就不会改变)、locale和布局的direction。

    有了新的数据就要保存,保存在configuration中不是个事。对于Android系统而言,改变语言,有两个地方的数据需要更新,一个是SystemProperties,另一个是数据库。前者以键值对的形式存放数据,多用于System,后者保存于DataBase中,多用于应用程序获取,算是对外开放的数据。上面方法中对这两个地方都进行了数据保存操作:
    1)SystemProperties:调用saveLocaleLocked()方法:
  1. /**
  2. * Save the locale.  You must be inside a synchronized (this) block.
  3. */
  4. private void saveLocaleLocked(Locale l, boolean isDiff, boolean isPersist) {
  5. if(isDiff) {
  6. SystemProperties.set("user.language", l.getLanguage());
  7. SystemProperties.set("user.region", l.getCountry());
  8. }
  9. if(isPersist) {
  10. SystemProperties.set("persist.sys.language", l.getLanguage());
  11. SystemProperties.set("persist.sys.country", l.getCountry());
  12. SystemProperties.set("persist.sys.localevar", l.getVariant());
  13. mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG, l));
  14. }
  15. }
    2)database:调用Settings.System.putConfiguration()方法:
  1. if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
  2. Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
  3. msg.obj = new Configuration(configCopy);
  4. mHandler.sendMessage(msg);
  5. }
  6. ...
  7. case UPDATE_CONFIGURATION_MSG: {
  8. final ContentResolver resolver = mContext.getContentResolver();
  9. Settings.System.putConfiguration(resolver, (Configuration)msg.obj);
  10. } break;
    该保存的数据保存了,但是Resource还不知道这档子事,因为Android代码和资源是分开的,Resource不知道Configuration发生了变化,Resource就不会去加载正确的资源。所以接下来此方法调用了mSystemThread.applyConfigurationToResources(configCopy)来完成这件事,mSystemThread是一个ActivityThread对象,其初始化在ActivityManagerService的构造函数中完成:   
  1. mSystemThread = ActivityThread.currentActivityThread();
        
  1. //此方法属于ActivityThread
  2. public final void applyConfigurationToResources(Configuration config) {
  3. synchronized (mResourcesManager) {
  4. mResourcesManager.applyConfigurationToResourcesLocked(config, null);
  5. }
  6. }
  7. //此方法属于ResourcesManage
  8. public final boolean applyConfigurationToResourcesLocked(Configuration config,
  9. CompatibilityInfo compat) {
  10. if (mResConfiguration == null) {
  11. mResConfiguration = new Configuration();
  12. }
  13. if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
  14. if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
  15. + mResConfiguration.seq + ", newSeq=" + config.seq);
  16. return false;
  17. }
  18. int changes = mResConfiguration.updateFrom(config);
  19. flushDisplayMetricsLocked();
  20. DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
  21. if (compat != null && (mResCompatibilityInfo == null ||
  22. !mResCompatibilityInfo.equals(compat))) {
  23. mResCompatibilityInfo = compat;
  24. changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
  25. | ActivityInfo.CONFIG_SCREEN_SIZE
  26. | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
  27. }
  28. // set it for java, this also affects newly created Resources
  29. if (config.locale != null) {
  30. Locale.setDefault(config.locale);
  31. }
  32. Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
  33. ApplicationPackageManager.configurationChanged();
  34. //Slog.i(TAG, "Configuration changed in " + currentPackageName());
  35. Configuration tmpConfig = null;
  36. for (int i=mActiveResources.size()-1; i>=0; i--) {
  37. ResourcesKey key = mActiveResources.keyAt(i);
  38. Resources r = mActiveResources.valueAt(i).get();
  39. if (r != null) {
  40. if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
  41. + r + " config to: " + config);
  42. int displayId = key.mDisplayId;
  43. boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
  44. DisplayMetrics dm = defaultDisplayMetrics;
  45. final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
  46. if (!isDefaultDisplay || hasOverrideConfiguration) {
  47. if (tmpConfig == null) {
  48. tmpConfig = new Configuration();
  49. }
  50. tmpConfig.setTo(config);
  51. if (!isDefaultDisplay) {
  52. dm = getDisplayMetricsLocked(displayId);
  53. applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
  54. }
  55. if (hasOverrideConfiguration) {
  56. tmpConfig.updateFrom(key.mOverrideConfiguration);
  57. }
  58. r.updateConfiguration(tmpConfig, dm, compat);
  59. } else {
  60. r.updateConfiguration(config, dm, compat);
  61. }
  62. //Slog.i(TAG, "Updated app resources " + v.getKey()
  63. //        + " " + r + ": " + r.getConfiguration());
  64. } else {
  65. //Slog.i(TAG, "Removing old resources " + v.getKey());
  66. mActiveResources.removeAt(i);
  67. }
  68. }
  69. return changes != 0;
  70. }
    此方法中Resource和ApplicationPackageManager都会去更新configuration,configuration所包含的属性都会遍历到,该更新的数据更新,该清除的缓存清除。
    到这里,第一件事算是做完了,就要做第二件事,让新的configuration更新到所有界面,updateConfigurationLocked()方法通过遍历保存在ProcessRecord中的进程,然后通过scheduleConfigurationChanged()方法更新它们的configuration:
  1. for (int i=mLruProcesses.size()-1; i>=0; i--) {
  2. ProcessRecord app = mLruProcesses.get(i);
  3. try {
  4. if (app.thread != null) {
  5. if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "
  6. + app.processName + " new config " + mConfiguration);
  7. app.thread.scheduleConfigurationChanged(configCopy);
  8. }
  9. } catch (Exception e) {
  10. }
  11. }
  此处通过Binder机制调用ApplicationThreadNative.java中的scheduleConfigurationChanged()方法,最后调用到ActivityThread中的内部类ApplicationThread的scheduleConfigurationChanged()方法,函数调用堆栈如图:
 Android-语言设置流程分析   Android-语言设置流程分析
  1. public void scheduleConfigurationChanged(Configuration config) {
  2. updatePendingConfiguration(config);
  3. sendMessage(H.CONFIGURATION_CHANGED, config);
  4. }
      
  1. case CONFIGURATION_CHANGED:
  2. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
  3. mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
  4. handleConfigurationChanged((Configuration)msg.obj, null);
  5. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  6. break;
 
  1. final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
  2. int configDiff = 0;
  3. synchronized (mResourcesManager) {
  4. if (mPendingConfiguration != null) {
  5. if (!mPendingConfiguration.isOtherSeqNewer(config)) {
  6. config = mPendingConfiguration;
  7. mCurDefaultDisplayDpi = config.densityDpi;
  8. updateDefaultDensity();
  9. }
  10. mPendingConfiguration = null;
  11. }
  12. if (config == null) {
  13. return;
  14. }
  15. if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
  16. + config);
  17. mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
  18. if (mConfiguration == null) {
  19. mConfiguration = new Configuration();
  20. }
  21. if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
  22. return;
  23. }
  24. configDiff = mConfiguration.diff(config);
  25. mConfiguration.updateFrom(config);
  26. config = applyCompatConfiguration(mCurDefaultDisplayDpi);
  27. }
  28. ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);
  29. freeTextLayoutCachesIfNeeded(configDiff);
  30. if (callbacks != null) {
  31. final int N = callbacks.size();
  32. for (int i=0; i<N; i++) {
  33. performConfigurationChanged(callbacks.get(i), config);
  34. }
  35. }
  36. }
    到这里设置语言以后,代码跑的流程就基本结束了,需要一提的是performConfigurationChanged()方法。为什么要提它呢?因为有时候写应用的时候activity需要关注一些configChanged,如:android:configChanges="orientation|keyboardHidden|screenSize",然后重写onConfigurationChanged()方法。然而触发这个方法回调的触发点在哪里呢?这里就以设置语言为例,设置语言触发了configuration的改变。先来看下performConfigurationChanged()方法:    
  1. private static void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) {
  2. // Only for Activity objects, check that they actually call up to their
  3. // superclass implementation.  ComponentCallbacks2 is an interface, so
  4. // we check the runtime type and act accordingly.
  5. Activity activity = (cb instanceof Activity) ? (Activity) cb : null;
  6. if (activity != null) {
  7. activity.mCalled = false;
  8. }
  9. boolean shouldChangeConfig = false;
  10. if ((activity == null) || (activity.mCurrentConfig == null)) {
  11. shouldChangeConfig = true;
  12. } else {
  13. // If the new config is the same as the config this Activity
  14. // is already running with then don't bother calling
  15. // onConfigurationChanged
  16. int diff = activity.mCurrentConfig.diff(config);
  17. if (diff != 0) {
  18. // If this activity doesn't handle any of the config changes
  19. // then don't bother calling onConfigurationChanged as we're
  20. // going to destroy it.
  21. if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) {
  22. shouldChangeConfig = true;
  23. }
  24. }
  25. }
  26. if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb
  27. + ": shouldChangeConfig=" + shouldChangeConfig);
  28. if (shouldChangeConfig) {
  29. cb.onConfigurationChanged(config);
  30. if (activity != null) {
  31. if (!activity.mCalled) {
  32. throw new SuperNotCalledException(
  33. "Activity " + activity.getLocalClassName() +
  34. " did not call through to super.onConfigurationChanged()");
  35. }
  36. activity.mConfigChangeFlags = 0;
  37. activity.mCurrentConfig = new Configuration(config);
  38. }
  39. }
  40. }
    如果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。具体就不多说了。
    
上一篇:从一道题浅说 JavaScript 的事件循环


下一篇:Spring MVC框架下的第一个Hello World程序