android源码分析(一) - 语言切换机制

来源:互联网 发布:win10解压缩软件rar 编辑:程序博客网 时间:2024/04/28 04:01

android语言切换是在packages/apps/Settings/com/android/settings/LocalePicker.java的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.locale = locale;            // indicate this isn't some passing default - the user wants this remembered            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        }    }
 从注释可以看出, 只要本地local改变就会调用该函数. 查看ActivityManagerNative的getDefault()可以看到, 该函数返回的是远程服务对象ActivityManagerServices.java在本地的一个代理.  最终调用的是ActivityManagerService.java中的updateConfiguration()函数. 

public void updateConfiguration(Configuration values) {        enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,                "updateConfiguration()");        synchronized(this) {            if (values == null && mWindowManager != null) {                // sentinel: fetch the current configuration from the window manager                values = mWindowManager.computeNewConfiguration();            }            if (mWindowManager != null) {                mProcessList.applyDisplaySize(mWindowManager);            }            final long origId = Binder.clearCallingIdentity();            if (values != null) {                Settings.System.clearConfiguration(values);            }            updateConfigurationLocked(values, null, false, false);            Binder.restoreCallingIdentity(origId);        }    }
 该函数, 首先进行的是权限的校验. 然后调用updateConfigurationLocked()函数. 

 

/**     * Do either or both things: (1) change the current configuration, and (2)     * make sure the given activity is running with the (now) current     * configuration.  Returns true if the activity has been left running, or     * false if <var>starting</var> is being destroyed to match the new     * configuration.     * @param persistent TODO     */    public boolean updateConfigurationLocked(Configuration values,            ActivityRecord starting, boolean persistent, boolean initLocale) {        int changes = 0;                boolean kept = true;                if (values != null) {            Configuration newConfig = new Configuration(mConfiguration);            changes = newConfig.updateFrom(values);            if (changes != 0) {                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {                    Slog.i(TAG, "Updating configuration to: " + values);                }                                EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);                if (values.locale != null && !initLocale) {                    saveLocaleLocked(values.locale,                                      !values.locale.equals(mConfiguration.locale),                                     values.userSetLocale, values.simSetLocale);                }                                mConfigurationSeq++;                if (mConfigurationSeq <= 0) {                    mConfigurationSeq = 1;                }                newConfig.seq = mConfigurationSeq;                mConfiguration = newConfig;                Slog.i(TAG, "Config changed: " + newConfig);                final Configuration configCopy = new Configuration(mConfiguration);                AttributeCache ac = AttributeCache.instance();                if (ac != null) {                    ac.updateConfiguration(configCopy);                }                // Make sure all resources in our process are updated                // right now, so that anyone who is going to retrieve                // resource values after we return will be sure to get                // the new ones.  This is especially important during                // boot, where the first config change needs to guarantee                // all resources have that config before following boot                // code is executed.                mSystemThread.applyConfigurationToResources(configCopy);                if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {                    Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);                    msg.obj = new Configuration(configCopy);                    mHandler.sendMessage(msg);                }                        for (int i=mLruProcesses.size()-1; i>=0; i--) {                    ProcessRecord app = mLruProcesses.get(i);                    try {                        if (app.thread != null) {                            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "                                    + app.processName + " new config " + mConfiguration);                            app.thread.scheduleConfigurationChanged(configCopy);                        }                    } catch (Exception e) {                    }                }                Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY                        | Intent.FLAG_RECEIVER_REPLACE_PENDING);                broadcastIntentLocked(null, null, intent, null, null, 0, null, null,                        null, false, false, MY_PID, Process.SYSTEM_UID);                if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {                    broadcastIntentLocked(null, null,                            new Intent(Intent.ACTION_LOCALE_CHANGED),                            null, null, 0, null, null,                            null, false, false, MY_PID, Process.SYSTEM_UID);                }                            }        }                if (changes != 0 && starting == null) {            // If the configuration changed, and the caller is not already            // in the process of starting an activity, then find the top            // activity to check if its configuration needs to change.            starting = mMainStack.topRunningActivityLocked(null);        }                if (starting != null) {            kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);            // And we need to make sure at this point that all other activities            // are made visible with the correct configuration.            mMainStack.ensureActivitiesVisibleLocked(starting, changes);        }                if (values != null && mWindowManager != null) {            mWindowManager.setNewConfiguration(mConfiguration);        }                return kept;    }

整个语言切换就在这个函数中完成. 咋一看似乎没感觉到该函数做了哪些事情. 我们首先来看注释: Do either or both things: (1) change the current configuration, and (2)
 make sure the given activity is running with the (now) current. configuration大概意思是: 这个函数做了两件事情. (1). 改变当前的configuration. 意思就是让改变的configuration更新到当前configuration. (2) 确保所有正在运行的activity都能更新改变后的configuration.(这点是关键.) . 我们按照这个思路看看android是如何更新configuration. 查看代码 , 首先看到 这个函数首先判断values是否为空, 这里values肯定不为空的, 然后changes = newConfig.updateFrom(values); 我们看看updateFrom做了什么操作.
/**     * Copy the fields from delta into this Configuration object, keeping     * track of which ones have changed.  Any undefined fields in     * <var>delta</var> are ignored and not copied in to the current     * Configuration.     * @return Returns a bit mask of the changed fields, as per     * {@link #diff}.     */    public int updateFrom(Configuration delta) {        int changed = 0;        ...        if (delta.locale != null                && (locale == null || !locale.equals(delta.locale))) {            changed |= ActivityInfo.CONFIG_LOCALE;            locale = delta.locale != null                    ? (Locale) delta.locale.clone() : null;            textLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(locale);        }        if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))        {            userSetLocale = true;            changed |= ActivityInfo.CONFIG_LOCALE;        }        ...        return changed;    }

因为语言改变了, 那么 (!locale.equals(delta.locale)) 是true. changed 大于0, 然后return changed. 回到ActivityManagerService.java的updateConfigurationLocked函数, 因为changed不为0 , 所以走if这个流程.  继续看代码

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


首先看到的是mLurProcesses 是ArrayList<ProcessRecord>类型.  LRU : Least Recently Used保存所有运行过的进程.  ProcessRecord进程类, 一个apk文件运行时会对应一个进程. app.thread. 此处的thread代表的是ApplicationThreadNative.java类型.  然后调用其scheduleConfigurationChanged();  查看该函数

public final void scheduleConfigurationChanged(Configuration config)            throws RemoteException {        Parcel data = Parcel.obtain();        data.writeInterfaceToken(IApplicationThread.descriptor);        config.writeToParcel(data, 0);        mRemote.transact(SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION, data, null,                IBinder.FLAG_ONEWAY);        data.recycle();    }
 
又是通过binder调用, 所以 , binder在android中是一个很重要的概念. 此处远程调用的是ActivityThread.java中的私有内部内ApplicationThread

  private class ApplicationThread extends ApplicationThreadNative {        private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s";        private static final String ONE_COUNT_COLUMN = "%21s %8d";        private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";        private static final String TWO_COUNT_COLUMNS_DB = "%21s %8d %21s %8d";        private static final String DB_INFO_FORMAT = "  %8s %8s %14s %14s  %s";        ...        public void scheduleConfigurationChanged(Configuration config) {            updatePendingConfiguration(config);            queueOrSendMessage(H.CONFIGURATION_CHANGED, config);        }        ...}

而ApplicationThread中的handler的CONFIGURATION_CHANGED是调用handleConfigurationChanged()

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

这个函数首先是调用applyConfigurationToResourcesLocked(). 看函数名大概可以推测: 将configuration应用到resources.这里configuration改变的是local 本地语言. 那而resources资源包含不就包含了语言, 图片这些资源吗. 

final boolean applyConfigurationToResourcesLocked(Configuration config,            CompatibilityInfo compat) {                int changes = mResConfiguration.updateFrom(config);        DisplayMetrics dm = getDisplayMetricsLocked(null, true);        if (compat != null && (mResCompatibilityInfo == null ||                !mResCompatibilityInfo.equals(compat))) {            mResCompatibilityInfo = compat;            changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT                    | ActivityInfo.CONFIG_SCREEN_SIZE                    | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;        }        ...        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);                //Slog.i(TAG, "Updated app resources " + v.getKey()                //        + " " + r + ": " + r.getConfiguration());            } else {                //Slog.i(TAG, "Removing old resources " + v.getKey());                it.remove();            }        }                return changes != 0;    }

Resources.updateSystemConfiguration()清除一部分系统资源, 并且将config更新到Resources, 而Resources包含了一个AssetManager对象, 该对象的核心实现是在AssetManager.cpp中完成的. 然后循环清空mActivityResources资源. 再回到handleConfigurationChanged()函数, 执行完updateSystemConfiguration后, 会循环该进程的所有activity:

if (callbacks != null) {

            final int N = callbacks.size();
            for (int i=0; i<N; i++) {
                performConfigurationChanged(callbacks.get(i), config);
            }
        }

再来看performConfigurationChanged的实现:

private final void performConfigurationChanged(            ComponentCallbacks2 cb, Configuration config) {        // Only for Activity objects, check that they actually call up to their        // superclass implementation.  ComponentCallbacks2 is an interface, so        // we check the runtime type and act accordingly.        Activity activity = (cb instanceof Activity) ? (Activity) cb : null;        if (activity != null) {            activity.mCalled = false;        }        boolean shouldChangeConfig = false;        if ((activity == null) || (activity.mCurrentConfig == null)) {            shouldChangeConfig = true;        } else {            // If the new config is the same as the config this Activity            // is already running with then don't bother calling            // onConfigurationChanged            int diff = activity.mCurrentConfig.diff(config);            if (diff != 0) {                // If this activity doesn't handle any of the config changes                // then don't bother calling onConfigurationChanged as we're                // going to destroy it.                if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) {                    shouldChangeConfig = true;                }            }        }        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb                + ": shouldChangeConfig=" + shouldChangeConfig);        if (shouldChangeConfig) {            cb.onConfigurationChanged(config);            if (activity != null) {                if (!activity.mCalled) {                    throw new SuperNotCalledException(                            "Activity " + activity.getLocalClassName() +                        " did not call through to super.onConfigurationChanged()");                }                activity.mConfigChangeFlags = 0;                activity.mCurrentConfig = new Configuration(config);            }        }    }

该函数判断configuration是否改变, 如果改变那么shouldChangeConfig为true. 然后调用activity的onConfigurationChange(config);

 /**     * Called by the system when the device configuration changes while your     * activity is running.  Note that this will <em>only</em> be called if     * you have selected configurations you would like to handle with the     * {@link android.R.attr#configChanges} attribute in your manifest.  If     * any configuration change occurs that is not selected to be reported     * by that attribute, then instead of reporting it the system will stop     * and restart the activity (to have it launched with the new     * configuration).     *      * <p>At the time that this function has been called, your Resources     * object will have been updated to return resource values matching the     * new configuration.     *      * @param newConfig The new device configuration.     */    public void onConfigurationChanged(Configuration newConfig) {        mCalled = true;        mFragments.dispatchConfigurationChanged(newConfig);        if (mWindow != null) {            // Pass the configuration changed event to the window            mWindow.onConfigurationChanged(newConfig);        }        if (mActionBar != null) {            // Do this last; the action bar will need to access            // view changes from above.            mActionBar.onConfigurationChanged(newConfig);        }    }

查看注释, 大概意思是:  如果你的activity运行 , 设备信息有改变(即configuration改变)时由系统调用. 如果你在manifest.xml中配置了configChnages属性则表示有你自己来处理configuration change. 否则就重启当前这个activity.  而重启之前, 旧的resources已经被清空, 那么就会装载新的资源, 整个过程就完成了语言切换后 , 能够让所有app使用新的语言. 语言切换流程大概分为三步:

第一步:  判断configuration的local是否已经改变, 如果改变则将local更新到当前的configuration

第二步: 清空旧的资源. 

第三步: 重启所有所有进程并加装新资源.

由于个人知识水平有限, 有些地方不免有些纰漏, 希望大牛多多指点.  也希望有共同兴趣爱好的人进行技术交流.



原创粉丝点击