前言

前篇阐述了Android语言切换的方法,那么这一篇文章我们就深入了解一下Android语言切换的原理吧。


正文

1、进入语言切换的入口类LocalePicker,找到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();

            config.setLocale(locale);
            config.userSetLocale = true;

            am.updateConfiguration(config);
            // Trigger the dirty bit for the Settings Provider.
            BackupManager.dataChanged("com.android.providers.settings");
        } catch (RemoteException e) {
            // Intentionally left blank
        }
    }

当locale发生改变的时候就会请求系统更新语言设置。ActivityManagerNative.getDefault()获取了ActivityManagerService的代理类,然后调这个类的updateConfiguration(config)的方法。


public void updateConfiguration(Configuration values) {
//进行权限校验
        enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,  
                "updateConfiguration()");  
  
        synchronized(this) {
            final long origId = Binder.clearCallingIdentity();  
            if (values != null) {  
                Settings.System.clearConfiguration(values);  
            }  
            updateConfigurationLocked(values, null, false, false);  
            Binder.restoreCallingIdentity(origId);  
        }  
    }

首先会进行权限的校验,然后清除Binder调用标志,再通过Settings.System.clearConfiguration(values)清除原有Configuration参数配置,然后再赋予新的参数配置。最后调用updateConfigurationLocaked()方法。

第一段主要方法newConfig.updateFrom(values)

/** 
     * 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 
     */  
    public 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) {  
               
               .....   
              
                mConfigurationSeq++;  
                if (mConfigurationSeq <= 0) {  
                    mConfigurationSeq = 1;  
                }  
                newConfig.seq = mConfigurationSeq;  
                mConfiguration = newConfig;  
               
  
                final Configuration configCopy = new Configuration(mConfiguration);  
  
                AttributeCache ac = AttributeCache.instance();  
                if (ac != null) {  
                    ac.updateConfiguration(configCopy);  
                }
第二段主要关注for循环
// 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) {  
                    }  
                }  
                ......
            }  
        }

把此方法截取分为两段来看,第一段最主要的方法就是newConfig.update()方法,这个方法判断我们修改了语言列表,如果修改了,就返回大于0的值。第二段就是for循环相关的了,这一段主要就是遍历所有的应用,然后对每个应用调用scheduleConfigurationChanged()方法进行语言切换。


public @Config int updateFrom(@NonNull Configuration delta) {
        //初始化变量,用于判断语言是否变化
        int changed = 0;
  
        ...

        if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
        {
            changed |= ActivityInfo.CONFIG_LOCALE;
            userSetLocale = true;
        }
	return changed;
    }


我们再查看一下

scheduleConfigurationChanged()这个方法,这个方法是通过binder远程调用在

ActivityThread中的方法,这个方法主要执行了updatePendingConfiguration()和queueOrSendMessage()两个方法。其中updatePendingConfiguration()就是用于更新Configuration的,queueOrSendMessage()是通过调用handleConfigurationChanged()实现功能的。

public void scheduleConfigurationChanged(Configuration config) {  
            updatePendingConfiguration(config);  
            queueOrSendMessage(H.CONFIGURATION_CHANGED, config);  
        }

下面是for循环内的

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) {  
                    }  
                }


我们再查看一下scheduleConfigurationChanged()这个方法,这个方法是通过binder远程调用在

ActivityThread中的方法,这个方法主要执行了updatePendingConfiguration()和queueOrSendMessage()两个方法。其中updatePendingConfiguration()就是用于更新Configuration的,queueOrSendMessage()是通过调用handleConfigurationChanged()实现功能的。

public void scheduleConfigurationChanged(Configuration config) {  
            updatePendingConfiguration(config);  
            queueOrSendMessage(H.CONFIGURATION_CHANGED, config);  
        }


我们接着查看handleConfigurationChanged()这个方法,在这个方法中主要有applyConfigurationToResourcesLocked()和performConfigurationChanged()。上一个方法主要重新将Configuration的参数配置到资源上面,后一个方法主要是执行Configuration的改变、完成语言的切换。


final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {  
   
       applyConfigurationToResourcesLocked(config, compat);  
       
       if (callbacks != null) {  
           final int N = callbacks.size();  
           for (int i=0; i<N; i++) {  
               performConfigurationChanged(callbacks.get(i), config);  
           }  
       }



在applyConfigurationToResuourcesLocked()中,Resources.updateSystemConfiguration()将config更新到Resource上面,在通过while循环删除mActiveResources旧的资源,更新新的资源。


final boolean applyConfigurationToResourcesLocked(Configuration config,  
            CompatibilityInfo compat) {  
      
        DisplayMetrics dm = getDisplayMetricsLocked(null, true);  
 
  
        Resources.updateSystemConfiguration(config, dm, compat);  
 
          
        Iterator<WeakReference<Resources>> it = mActiveResources.values().iterator();  
        while (it.hasNext()) {  
            WeakReference<Resources> v = it.next();  
            Resources r = v.get();  
            if (r != null) {  
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "  
                        + r + " config to: " + config);  
                r.updateConfiguration(config, dm, compat);  
       
            } else {  
                it.remove();  
            }  
        }  
   
    }


对于performConfigurationChanged()这个方法,判断activity的config和传递过来的config是否一样,如果不一样就将shouldChangeConfig=true,如果shouldChangeConfig=true,就执行activity的onConfigurationChanged(config)方法。


private final void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) {  
     
        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);  
   
        }  
    }

总结



其实就是判断Configuration的locale有没有改变,有改变就清除旧的Resource资源,并将新的config赋予Resource,并重新执行加载Resource资源。