Android-语言设置流程分析

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

    以Android5.1工程源码为基础,从设置app入手来分析和学习语言切换的过程:
    一、语言设置界面:
    首先在设置app中找到语言设置这个Preference,目前设置中界面大多都是Fragment,先找到语言和输入法的PreferenceScreen,与其对应的Fragment是InputMethodAndLanguageSettings.java,在其onCreate()方法中,首先是增加语言设置的preference:    
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. addPreferencesFromResource(R.xml.language_settings);  
找到language_settings.xml,可发现如下代码:   
[html] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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:   
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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:
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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()方法获取系统支持语言的信息:
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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.   
  8.         // Don't show the pseudolocales unless we're in developer mode.   
  9.         if (!isInDeveloperMode) {  
  10.             localeList.remove("ar-XB");  
  11.             localeList.remove("en-XA");  
  12.         }  
  13.   
  14.         Collections.sort(localeList);  
  15.         final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);  
  16.         final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);  
  17.   
  18.         final ArrayList<LocaleInfo> localeInfos = new ArrayList<LocaleInfo>(localeList.size());  
  19.         for (String locale : localeList) {  
  20.             final Locale l = Locale.forLanguageTag(locale.replace('_', '-'));  
  21.             if (l == null || "und".equals(l.getLanguage())  
  22.                     || l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {  
  23.                 continue;  
  24.             }  
  25.   
  26.             if (localeInfos.isEmpty()) {  
  27.                 if (DEBUG) {  
  28.                     Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l)));  
  29.                 }  
  30.                 localeInfos.add(new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l));  
  31.             } else {  
  32.                 // check previous entry:  
  33.                 //  same lang and a country -> upgrade to full name and  
  34.                 //    insert ours with full name  
  35.                 //  diff lang -> insert ours with lang-only name  
  36.                 final LocaleInfo previous = localeInfos.get(localeInfos.size() - 1);  
  37.                 if (previous.locale.getLanguage().equals(l.getLanguage()) &&  
  38.                         !previous.locale.getLanguage().equals("zz")) {  
  39.                     if (DEBUG) {  
  40.                         Log.v(TAG, "backing up and fixing " + previous.label + " to " +  
  41.                                 getDisplayName(previous.locale, specialLocaleCodes, specialLocaleNames));  
  42.                     }  
  43.                     previous.label = toTitleCase(getDisplayName(  
  44.                             previous.locale, specialLocaleCodes, specialLocaleNames));  
  45.                     if (DEBUG) {  
  46.                         Log.v(TAG, "  and adding "+ toTitleCase(  
  47.                                 getDisplayName(l, specialLocaleCodes, specialLocaleNames)));  
  48.                     }  
  49.                     localeInfos.add(new LocaleInfo(toTitleCase(  
  50.                             getDisplayName(l, specialLocaleCodes, specialLocaleNames)), l));  
  51.                 } else {  
  52.                     String displayName = toTitleCase(l.getDisplayLanguage(l));  
  53.                     if (DEBUG) {  
  54.                         Log.v(TAG, "adding "+displayName);  
  55.                     }  
  56.                     localeInfos.add(new LocaleInfo(displayName, l));  
  57.                 }  
  58.             }  
  59.         }  
  60.   
  61.         Collections.sort(localeInfos);  
  62.         return localeInfos;  
  63.     }  
    此方法中还会通过Resources.getSystem().getAssets().getLocales()去获得系统支持的语言信息,然后添加LocaleInfo里边,再通过Adapter适配到ListView中。getLocales()方法属于类AssetManager.java:
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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.1frameworksasecorejni,对应文件:android_util_AssetManager.cpp(浏览下这个文件,发现这个家伙有点不得了啊,什么resource,theme等都跟它有关系,看样子还的加油学学JNI啊!),然后找到对应的native方法:
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz)  
  2. {  
  3.     Vector<String8> locales;  
  4.   
  5.     AssetManager* am = assetManagerForJavaObject(env, clazz);  
  6.     if (am == NULL) {  
  7.         return NULL;  
  8.     }  
  9.   
  10.     am->getLocales(&locales);  
  11.   
  12.     const int N = locales.size();  
  13.   
  14.     jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL);  
  15.     if (result == NULL) {  
  16.         return NULL;  
  17.     }  
  18.   
  19.     for (int i=0; i<N; i++) {  
  20.         jstring str = env->NewStringUTF(locales[i].string());  
  21.         if (str == NULL) {  
  22.             return NULL;  
  23.         }  
  24.         env->SetObjectArrayElement(result, i, str);  
  25.         env->DeleteLocalRef(str);  
  26.     }  
  27.   
  28.     return result;  
  29. }  
通过上面初步的分析,语言的List界面就基本出来了,在getAllAssetLocales()方法中打了个断点,查看了下locales被赋值以后的值:
二、语言设置功能实现过程:
上面提到了设置中的LocalePicker类实现了父类接口中的onLocaleSelected()方法:   
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public static interface LocaleSelectionListener {  
  2.        // You can add any argument if you really need it...  
  3.        public void onLocaleSelected(Locale locale);  
  4.    }      
  5.      
  6.    @Override  
  7.    public void onLocaleSelected(final Locale locale) {  
  8.        if (Utils.hasMultipleUsers(getActivity())) {  
  9.            mTargetLocale = locale;  
  10.            showDialog(DLG_SHOW_GLOBAL_WARNING);  
  11.        } else {  
  12.            getActivity().onBackPressed();  
  13.            LocalePicker.updateLocale(locale);  
  14.        }  
  15.    }  
此方法中最终调用了其父类的updateLocale()方法来更新系统的语言环境:  
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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()方法:
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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清除了额,其实这个方法只是把系统字体的特效清除了,比如字体的大小:   
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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()方法:
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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.                  
  20.                 EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);  
  21.                 if (values.locale != null && !initLocale) {  
  22.                     saveLocaleLocked(values.locale,  
  23.                                      !values.locale.equals(mConfiguration.locale),  
  24.                                      values.userSetLocale);  
  25.                 }  
  26.                 mConfigurationSeq++;  
  27.                 if (mConfigurationSeq <= 0) {  
  28.                     mConfigurationSeq = 1;  
  29.                 }  
  30.                 newConfig.seq = mConfigurationSeq;  
  31.                 mConfiguration = newConfig;  
  32.                 Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);  
  33.                 mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId);  
  34.                 //mUsageStatsService.noteStartConfig(newConfig);  
  35.                 final Configuration configCopy = new Configuration(mConfiguration);  
  36.                  
  37.                 // TODO: If our config changes, should we auto dismiss any currently  
  38.                 // showing dialogs?  
  39.                 mShowDialogs = shouldShowDialogs(newConfig);  
  40.                 AttributeCache ac = AttributeCache.instance();  
  41.                 if (ac != null) {  
  42.                     ac.updateConfiguration(configCopy);  
  43.                 }  
  44.                 // Make sure all resources in our process are updated  
  45.                 // right now, so that anyone who is going to retrieve  
  46.                 // resource values after we return will be sure to get  
  47.                 // the new ones.  This is especially important during  
  48.                 // boot, where the first config change needs to guarantee  
  49.                 // all resources have that config before following boot  
  50.                 // code is executed.  
  51.                 mSystemThread.applyConfigurationToResources(configCopy);  
  52.                 if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {  
  53.                     Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);  
  54.                     msg.obj = new Configuration(configCopy);  
  55.                     mHandler.sendMessage(msg);  
  56.                 }  
  57.                 for (int i=mLruProcesses.size()-1; i>=0; i--) {  
  58.                     ProcessRecord app = mLruProcesses.get(i);  
  59.                     try {  
  60.                         if (app.thread != null) {  
  61.                             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "  
  62.                                     + app.processName + " new config " + mConfiguration);  
  63.                             app.thread.scheduleConfigurationChanged(configCopy);  
  64.                         }  
  65.                     } catch (Exception e) {  
  66.                     }  
  67.                 }  
  68.                 Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);  
  69.                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY  
  70.                         | Intent.FLAG_RECEIVER_REPLACE_PENDING  
  71.                         | Intent.FLAG_RECEIVER_FOREGROUND);  
  72.                 broadcastIntentLocked(null, null, intent, null, null, 0, null, null,  
  73.                         null, AppOpsManager.OP_NONE, false, false, MY_PID,  
  74.                         Process.SYSTEM_UID, UserHandle.USER_ALL);  
  75.                 if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {  
  76.                     intent = new Intent(Intent.ACTION_LOCALE_CHANGED);  
  77.                     intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);  
  78.                     broadcastIntentLocked(null, null, intent,  
  79.                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,  
  80.                             false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);  
  81.                 }  
  82.             }  
  83.         }  
  84.         boolean kept = true;  
  85.         final ActivityStack mainStack = mStackSupervisor.getFocusedStack();  
  86.         // mainStack is null during startup.  
  87.         if (mainStack != null) {  
  88.             if (changes != 0 && starting == null) {  
  89.                 // If the configuration changed, and the caller is not already  
  90.                 // in the process of starting an activity, then find the top  
  91.                 // activity to check if its configuration needs to change.  
  92.                 starting = mainStack.topRunningActivityLocked(null);  
  93.             }  
  94.             if (starting != null) {  
  95.                 kept = mainStack.ensureActivityConfigurationLocked(starting, changes);  
  96.                 // And we need to make sure at this point that all other activities  
  97.                 // are made visible with the correct configuration.  
  98.                 mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes);  
  99.             }  
  100.         }  
  101.         if (values != null && mWindowManager != null) {  
  102.             mWindowManager.setNewConfiguration(mConfiguration);  
  103.         }  
  104.         return kept;  
  105.     }  
此方法主要做两件事:第一,改变当前的configuration,将新的数据放进去。第二,保证正在运行的应用程序界面更新最新的configuration。先调用updateFrom()方法,遍历configuration包含的属性是否改变,如果有改变就返回一个对应的整数,如果没有改变就返回0。就语言改变而言,根据上面的分析,configuration至少有3个属性发生了改变:fontscale(之前没有设置字体的效果就不会改变)、locale和布局的direction。
    有了新的数据就要保存,保存在configuration中不是个事。对于Android系统而言,改变语言,有两个地方的数据需要更新,一个是SystemProperties,另一个是数据库。前者以键值对的形式存放数据,多用于System,后者保存于DataBase中,多用于应用程序获取,算是对外开放的数据。上面方法中对这两个地方都进行了数据保存操作:
    1)SystemProperties:调用saveLocaleLocked()方法:
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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()方法:
   
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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的构造函数中完成:   
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. mSystemThread = ActivityThread.currentActivityThread();  
        
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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:
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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()方法,函数调用堆栈如图:
    
    
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public void scheduleConfigurationChanged(Configuration config) {  
  2.             updatePendingConfiguration(config);  
  3.             sendMessage(H.CONFIGURATION_CHANGED, config);  
  4.     }  
      
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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;  
 
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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.              
  16.             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "  
  17.                     + config);  
  18.             mResourcesManager.applyConfigurationToResourcesLocked(config, compat);  
  19.             if (mConfiguration == null) {  
  20.                 mConfiguration = new Configuration();  
  21.             }  
  22.             if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {  
  23.                 return;  
  24.             }  
  25.             configDiff = mConfiguration.diff(config);  
  26.             mConfiguration.updateFrom(config);  
  27.             config = applyCompatConfiguration(mCurDefaultDisplayDpi);  
  28.         }  
  29.         ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);  
  30.         freeTextLayoutCachesIfNeeded(configDiff);  
  31.         if (callbacks != null) {  
  32.             final int N = callbacks.size();  
  33.             for (int i=0; i<N; i++) {  
  34.                 performConfigurationChanged(callbacks.get(i), config);  
  35.             }  
  36.         }  
  37.     }  
    到这里设置语言以后,代码跑的流程就基本结束了,需要一提的是performConfigurationChanged()方法。为什么要提它呢?因为有时候写应用的时候activity需要关注一些configChanged,如:android:configChanges="orientation|keyboardHidden|screenSize",然后重写onConfigurationChanged()方法。然而触发这个方法回调的触发点在哪里呢?这里就以设置语言为例,设置语言触发了configuration的改变。先来看下performConfigurationChanged()方法:    
[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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。具体就不多说了。
    
原文地址:https://www.cnblogs.com/Free-Thinker/p/5461523.html