语言切换流程解析

206 阅读4分钟

一. 语言切换时序图

20171119150013478.png

二. 关键步骤

1. Activity侧-发起端

/**
 * Requests the system to update the list of system locales.
 * 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 updateLocales(LocaleList locales) {
    try {
        //获取am
        final IActivityManager am = ActivityManagerNative.getDefault();
        //获取am配置对象
        final Configuration config = am.getConfiguration();

        //为配置对象重新设置语言
        config.setLocales(locales);
        //重置标志位
        config.userSetLocale = true;

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

2. AMS处理流程

/**
 * 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 userId is only used when persistent parameter is set to true to persist configuration
 *               for that particular user
 */
private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
        boolean initLocale, boolean persistent, int userId, boolean deferResume) {
    int changes = 0;
    ......
    if (values != null) {
        Configuration newConfig = new Configuration(mConfiguration);
        changes = newConfig.updateFrom(values);
        if (changes != 0) {
            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
                    "Updating configuration to: " + values);

            EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);

            if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
                //得到选择的国家语言列表
                final LocaleList locales = values.getLocales();
                int bestLocaleIndex = 0;
                if (locales.size() > 1) {
                    if (mSupportedSystemLocales == null) {
                        //获取系统支持国家语言列表
                        mSupportedSystemLocales =
                                Resources.getSystem().getAssets().getLocales();
                    }
                    //匹配国家,获取选择默认国家语言下标
                    bestLocaleIndex = Math.max(0,
                            locales.getFirstMatchIndex(mSupportedSystemLocales));
                }
                SystemProperties.set("persist.sys.locale",
                        locales.get(bestLocaleIndex).toLanguageTag());
                //设置为选择的国家语言为默认国家语言
                LocaleList.setDefault(locales, bestLocaleIndex);
                //发送消息通知挂载守护进程国家语言变更
                mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
                        locales.get(bestLocaleIndex)));
            }

            ...

            // 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);
                msg.arg1 = userId;
                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_CONFIGURATION, "Sending to proc "
                                + app.processName + " new config " + mConfiguration);
                        app.thread.scheduleConfigurationChanged(configCopy);
                    }
                } catch (Exception e) {
                }
            }

            ...
    }


语言变更后,通知所有当前正在运行的进程

//保存所有应用的进程
final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();

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_CONFIGURATION, "Sending to proc "
                    + app.processName + " new config " + mConfiguration);
            app.thread.scheduleConfigurationChanged(configCopy);
        }
    } catch (Exception e) {
    }
}

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();
}


3. Activity侧-AMS回调

public void scheduleConfigurationChanged(Configuration config) {
    updatePendingConfiguration(config);
    //发送消息给对应的主线程
    sendMessage(H.CONFIGURATION_CHANGED, config);
}

...

case CONFIGURATION_CHANGED:
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
    mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
    mUpdatingSystemConfig = true;
    //接收到AMS发来的数据
    handleConfigurationChanged((Configuration)msg.obj, null);
    mUpdatingSystemConfig = false;
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    break;


资源配置的变更


public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
                                                         @Nullable CompatibilityInfo compat) {

    private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
            new ArrayMap<>();

    try {

        ...

        //获取更新项
        int changes = mResConfiguration.updateFrom(config);
        // Things might have changed in display manager, so clear the cached displays.
        mDisplays.clear();

        ...

        //更新系统资源配置
        Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
        //通知配置文件修改,清理缓存,如Icon和String
        ApplicationPackageManager.configurationChanged();
        //Slog.i(TAG, "Configuration changed in " + currentPackageName());

        Configuration tmpConfig = null;

        for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
            ResourcesKey key = mResourceImpls.keyAt(i);
            ResourcesImpl r = mResourceImpls.valueAt(i).get();
            if (r != null) {

                    ...

                    r.updateConfiguration(tmpConfig, dm, compat);
                } else {
                    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());
                mResourceImpls.removeAt(i);
            }
        }

        return changes != 0;
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    }
}

相关的资源文件缓存进行清理--ApplicationPackageManager.java,因为缓存被清除了,所以下次再获取资源文件的时候,需要重新执行ResourcesManager.getRes().

 static void configurationChanged() {
        synchronized (sSync) {
            // 图片icon的缓存
            sIconCache.clear();
            // 字符串缓存,比如getText()获取到的字符串
            sStringCache.clear();
        }
    }

activity执行配置变更逻辑


private void performConfigurationChanged(ComponentCallbacks2 cb,
                                         IBinder activityToken,
                                         Configuration newConfig,
                                         Configuration amOverrideConfig,
                                         boolean reportToActivity) {
    // 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 and the override config also didn't change, then don't
        // bother calling onConfigurationChanged.
        int diff = activity.mCurrentConfig.diff(newConfig);
        if (diff != 0 || !mResourcesManager.isSameResourcesOverrideConfig(activityToken,
                amOverrideConfig)) {
            // Always send the task-level config changes. For system-level configuration, 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 (!mUpdatingSystemConfig
                    || (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
                    || !reportToActivity) {
                shouldChangeConfig = true;
            }
        }
    }

    if (shouldChangeConfig) {
        // Propagate the configuration change to the Activity and ResourcesManager.

        // ContextThemeWrappers may override the configuration for that context.
        // We must check and apply any overrides defined.
        Configuration contextThemeWrapperOverrideConfig = null;
        if (cb instanceof ContextThemeWrapper) {
            final ContextThemeWrapper contextThemeWrapper = (ContextThemeWrapper) cb;
            contextThemeWrapperOverrideConfig = contextThemeWrapper.getOverrideConfiguration();
        }

        // We only update an Activity's configuration if this is not a global
        // configuration change. This must also be done before the callback,
        // or else we violate the contract that the new resources are available
        // in {@link ComponentCallbacks2#onConfigurationChanged(Configuration)}.
        if (activityToken != null) {
            // Apply the ContextThemeWrapper override if necessary.
            // NOTE: Make sure the configurations are not modified, as they are treated
            // as immutable in many places.
            final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
                    amOverrideConfig, contextThemeWrapperOverrideConfig);
            mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig);
        }

        if (reportToActivity) {
            // Apply the ContextThemeWrapper override if necessary.
            // NOTE: Make sure the configurations are not modified, as they are treated
            // as immutable in many places.
            final Configuration configToReport = createNewConfigAndUpdateIfNotNull(
                    newConfig, contextThemeWrapperOverrideConfig);
            cb.onConfigurationChanged(configToReport);
        }

        if (activity != null) {
            if (reportToActivity && !activity.mCalled) {
                throw new SuperNotCalledException(
                        "Activity " + activity.getLocalClassName() +
                        " did not call through to super.onConfigurationChanged()");
            }
            activity.mConfigChangeFlags = 0;
            activity.mCurrentConfig = new Configuration(newConfig);
        }
    }
}


参考文档:blog.csdn.net/a282255307/…)