zoukankan      html  css  js  c++  java
  • 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。具体就不多说了。
        
  • 相关阅读:
    时间日期date/cal
    chown命令
    su命令
    which命令和bin目录
    python基础之文件操作
    python之模块之shutil模块
    python基础之面向对象01
    python基础之面向对象02
    python基础之map/reduce/filter/sorted
    python基础之模块之序列化
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/5461523.html
Copyright © 2011-2022 走看看