美文网首页
Android切换语言源码分析

Android切换语言源码分析

作者: ZenCabin | 来源:发表于2019-01-30 19:05 被阅读8次

当我们在系统设置-更多设置-语言和输入法中点击语言,在其中选择一种新的语言后,系统在框架层做了什么?带着这个问题,我们去看下系统的源码

ConfigurationChange Flow


ConfigurationChange Flow.png

修改系统configuration(包括语言、屏幕方向等)之后,会引起已经启动运行的进程的一系列变化。
打开本地路径:D:\android\sdk\sources\android-26\com\android\internal\app,其中类LocalePicker是原生系统切换语言的一个Fragment,然后看一下updateLocales(LocaleList locales) 。

    /**
     * 请求系统更新locale列表,需要注意,当改变Locale时系统看起来会停顿一下     
     */
    public static void updateLocales(LocaleList locales) {
        try {
            final IActivityManager am = ActivityManager.getService();
            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
        }
    }

只要本地locale改变便会调用该方法。ActivityManager.getService()返回的是远程服务对象ActivityManagerService.java在本地的一个代理,最终调用的是ActivityManagerService.java中的updatePersistentConfiguration()方法。

    @Override
    public void updatePersistentConfiguration(Configuration values) {
        enforceCallingPermission(CHANGE_CONFIGURATION, "updatePersistentConfiguration()");
        enforceWriteSettingsPermission("updatePersistentConfiguration()");
        if (values == null) {
            throw new NullPointerException("Configuration must not be null");
        }

        int userId = UserHandle.getCallingUserId();

        synchronized(this) {
            updatePersistentConfigurationLocked(values, userId);
        }
    }

这个方法首先检查权限,然后同步地调用updatePersistentConfigurationLocked方法。

    private void updatePersistentConfigurationLocked(Configuration values, @UserIdInt int userId) {
        final long origId = Binder.clearCallingIdentity();
        try {
            updateConfigurationLocked(values, null, false, true, userId, false /* deferResume */);
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

    private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
            boolean initLocale, boolean persistent, int userId, boolean deferResume) {
        return updateConfigurationLocked(values, starting, initLocale, persistent, userId,
                deferResume, null /* result */);
    }

    /**
     * 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,
            UpdateConfigurationResult result) {
        int changes = 0;
        boolean kept = true;

        if (mWindowManager != null) {
            mWindowManager.deferSurfaceLayout();
        }
        try {
            if (values != null) {
                changes = updateGlobalConfiguration(values, initLocale, persistent, userId,
                        deferResume);
            }

            kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
        } finally {
            if (mWindowManager != null) {
                mWindowManager.continueSurfaceLayout();
            }
        }

        if (result != null) {
            result.changes = changes;
            result.activityRelaunched = !kept;
        }
        return kept;
    }

由上面的updateConfigurationLocked()的注释开始看起,函数做了以下两件事情或者至少其中一个方面。

  1. 改变当前的configuration。
  2. 确保给定的activity正在运行当前的configuration。(这点是关键) 。

我们按照这个思路看看android是如何更新configuration. 查看代码 , 首先可以看到这个函数首先判断values是否为空, 这里values肯定不为空的, 然后调用updateGlobalConfiguration方法。

    /** Update default (global) configuration and notify listeners about changes. */
    private int updateGlobalConfiguration(@NonNull Configuration values, boolean initLocale,
            boolean persistent, int userId, boolean deferResume) {
        mTempConfig.setTo(getGlobalConfiguration());
        final int changes = mTempConfig.updateFrom(values);
        if (changes == 0) {
            // 由于调用Activity.setRequestedOrientation会导致window被冻结以及WindowManagerService.mWaitingForConfig被设置为true, 
            // 所以调用performDisplayOverrideConfigUpdate为了发送新的配置事件(即便是什么都没有改变) 去解冻window是重要的.
            performDisplayOverrideConfigUpdate(values, deferResume, DEFAULT_DISPLAY);
            return 0;
        }

        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
                "Updating global 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)));
        }

        mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
        mTempConfig.seq = mConfigurationSeq;

        // Update stored global config and notify everyone about the change.
        mStackSupervisor.onConfigurationChanged(mTempConfig);

        Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
        // TODO(multi-display): Update UsageEvents#Event to include displayId.
        mUsageStatsService.reportConfigurationChange(mTempConfig,
                mUserController.getCurrentUserIdLocked());

        // TODO: If our config changes, should we auto dismiss any currently showing dialogs?
        mShowDialogs = shouldShowDialogs(mTempConfig);

        AttributeCache ac = AttributeCache.instance();
        if (ac != null) {
            ac.updateConfiguration(mTempConfig);
        }

        // 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(mTempConfig);

        // We need another copy of global config because we're scheduling some calls instead of
        // running them in place. We need to be sure that object we send will be handled unchanged.
        final Configuration configCopy = new Configuration(mTempConfig);
        if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
            Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
            msg.obj = configCopy;
            msg.arg1 = userId;
            mHandler.sendMessage(msg);
        }

        // 遍历最近运行的进程,分别调用其scheduleConfigurationChanged方法
        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 " + configCopy);
                    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
                | Intent.FLAG_RECEIVER_FOREGROUND
                | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
        broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
                AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                UserHandle.USER_ALL);
        if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
            intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                    | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
            if (initLocale || !mProcessesReady) {
                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            }
            broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
                    AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                    UserHandle.USER_ALL);
        }

        // Override configuration of the default display duplicates global config, so we need to
        // update it also. This will also notify WindowManager about changes.
        performDisplayOverrideConfigUpdate(mStackSupervisor.getConfiguration(), deferResume,
                DEFAULT_DISPLAY);

        return changes;
    }

从注释看,其作用是更新默认(全局)的configuration,并通知相关监听类。
其中如下代码,遍历了mLruProcesses变量(其中存储了最近运行的app),并调用了app.thread.scheduleConfigurationChanged(configCopy);来通知每一个进程。app.thread类型为ApplicationThreadNative,是一个进程的实例。

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 " + configCopy);
            app.thread.scheduleConfigurationChanged(configCopy);
        }
    } catch (Exception e) {
    }
}

/**
 * List of running applications, sorted by recent usage.
 * The first entry in the list is the least recently used.
 */
final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();

我们接着进入scheduleConfigurationChanged这个方法
,奇怪在26的sources里没有找到ApplicationThreadNative,贴上25的

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,这里回调到ActivityThread内部的ApplicationThread类。接着贴,

private class ApplicationThread extends IApplicationThread.Stub {
。。。
    public void scheduleConfigurationChanged(Configuration config) {
        updatePendingConfiguration(config);
        sendMessage(H.CONFIGURATION_CHANGED, config);
    }
。。。
}

sendMessage之后,

public void handleMessage(Message msg) {
。。。
    case CONFIGURATION_CHANGED:
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
        mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
        mUpdatingSystemConfig = true;
        try {
            handleConfigurationChanged((Configuration) msg.obj, null);
        } finally {
            mUpdatingSystemConfig = false;
        }
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        break;
。。。
}


ActivityThread内部的handler收到消息,进入处理case,调用handleConfigurationChanged方法。


final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {

    int configDiff = 0;

    synchronized (mResourcesManager) {
        if (mPendingConfiguration != null) {
            if (!mPendingConfiguration.isOtherSeqNewer(config)) {
                config = mPendingConfiguration;
                mCurDefaultDisplayDpi = config.densityDpi;
                updateDefaultDensity();
            }
            mPendingConfiguration = null;
        }

        if (config == null) {
            return;
        }

        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
                + config);

        mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
        updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
                mResourcesManager.getConfiguration().getLocales());

        if (mConfiguration == null) {
            mConfiguration = new Configuration();
        }
        if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
            return;
        }

        configDiff = mConfiguration.updateFrom(config);
        config = applyCompatConfiguration(mCurDefaultDisplayDpi);

        final Theme systemTheme = getSystemContext().getTheme();
        if ((systemTheme.getChangingConfigurations() & configDiff) != 0) {
            systemTheme.rebase();
        }

        final Theme systemUiTheme = getSystemUiContext().getTheme();
        if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) {
            systemUiTheme.rebase();
        }
    }

    ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);

    freeTextLayoutCachesIfNeeded(configDiff);

    if (callbacks != null) {
        final int N = callbacks.size();
        for (int i=0; i<N; i++) {
            ComponentCallbacks2 cb = callbacks.get(i);
            if (cb instanceof Activity) {
                // If callback is an Activity - call corresponding method to consider override
                // config and avoid onConfigurationChanged if it hasn't changed.
                Activity a = (Activity) cb;
                performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
                        config);
            } else {
                performConfigurationChanged(cb, config);
            }
        }
    }
}

代码ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);中的collectComponentCallbacks方法手机了进程中所有的Application、Activity和Service实例,然后再遍历他们,分别调用其回调方法来通知其config的改变。

ArrayList<ComponentCallbacks2> collectComponentCallbacks(
        boolean allActivities, Configuration newConfig) {
    ArrayList<ComponentCallbacks2> callbacks
            = new ArrayList<ComponentCallbacks2>();

    synchronized (mResourcesManager) {
        final int NAPP = mAllApplications.size();
        for (int i=0; i<NAPP; i++) {
            callbacks.add(mAllApplications.get(i));
        }
        final int NACT = mActivities.size();
        for (int i=0; i<NACT; i++) {
            ActivityClientRecord ar = mActivities.valueAt(i);
            Activity a = ar.activity;
            if (a != null) {
                Configuration thisConfig = applyConfigCompatMainThread(
                        mCurDefaultDisplayDpi, newConfig,
                        ar.packageInfo.getCompatibilityInfo());
                if (!ar.activity.mFinished && (allActivities || !ar.paused)) {
                    // If the activity is currently resumed, its configuration
                    // needs to change right now.
                    callbacks.add(a);
                } else if (thisConfig != null) {
                    // Otherwise, we will tell it about the change
                    // the next time it is resumed or shown.  Note that
                    // the activity manager may, before then, decide the
                    // activity needs to be destroyed to handle its new
                    // configuration.
                    if (DEBUG_CONFIGURATION) {
                        Slog.v(TAG, "Setting activity "
                                + ar.activityInfo.name + " newConfig=" + thisConfig);
                    }
                    ar.newConfig = thisConfig;
                }
            }
        }
        final int NSVC = mServices.size();
        for (int i=0; i<NSVC; i++) {
            callbacks.add(mServices.valueAt(i));
        }
    }
    synchronized (mProviderMap) {
        final int NPRV = mLocalProviders.size();
        for (int i=0; i<NPRV; i++) {
            callbacks.add(mLocalProviders.valueAt(i).mLocalProvider);
        }
    }

    return callbacks;
}

再回去看下边的for循环,它遍历了所有组件的回调方法。

if (callbacks != null) {
    final int N = callbacks.size();
    for (int i=0; i<N; i++) {
        ComponentCallbacks2 cb = callbacks.get(i);
        if (cb instanceof Activity) {
            // 如果callback是一个activity的实例的话,考虑调用相应的方法覆盖旧的config,而避免调用onConfigurationChanged(如果它没有更改的话)。
            Activity a = (Activity) cb;
            performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
                    config);
        } else {
            performConfigurationChanged(cb, config);
        }
    }
}
/**
 * Updates the configuration for an Activity. The ActivityClientRecord's
 * {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
 * that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
 * the updated Configuration.
 * @param r ActivityClientRecord representing the Activity.
 * @param newBaseConfig The new configuration to use. This may be augmented with
 *                      {@link ActivityClientRecord#overrideConfig}.
 */
private void performConfigurationChangedForActivity(ActivityClientRecord r,
        Configuration newBaseConfig) {
    performConfigurationChangedForActivity(r, newBaseConfig,
            r.activity.getDisplay().getDisplayId(), false /* movedToDifferentDisplay */);
}

/**
 * Updates the configuration for an Activity. The ActivityClientRecord's
 * {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
 * that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
 * the updated Configuration.
 * @param r ActivityClientRecord representing the Activity.
 * @param newBaseConfig The new configuration to use. This may be augmented with
 *                      {@link ActivityClientRecord#overrideConfig}.
 * @param displayId The id of the display where the Activity currently resides.
 * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
 * @return {@link Configuration} instance sent to client, null if not sent.
 */
private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
        Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
    r.tmpConfig.setTo(newBaseConfig);
    if (r.overrideConfig != null) {
        r.tmpConfig.updateFrom(r.overrideConfig);
    }
    final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
            r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay);
    freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
    return reportedConfig;
}

/**
 * Decides whether to update an Activity's configuration and whether to inform it.
 * @param activity The activity to notify of configuration change.
 * @param newConfig The new configuration.
 * @param amOverrideConfig The override config that differentiates the Activity's configuration
 *                         from the base global configuration. This is supplied by
 *                         ActivityManager.
 * @param displayId Id of the display where activity currently resides.
 * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
 * @return Configuration sent to client, null if no changes and not moved to different display.
 */
private Configuration performActivityConfigurationChanged(Activity activity,
        Configuration newConfig, Configuration amOverrideConfig, int displayId,
        boolean movedToDifferentDisplay) {
    if (activity == null) {
        throw new IllegalArgumentException("No activity provided.");
    }
    final IBinder activityToken = activity.getActivityToken();
    if (activityToken == null) {
        throw new IllegalArgumentException("Activity token not set. Is the activity attached?");
    }

    // 判断是否应该回调onConfigurationChaned方法
    boolean shouldChangeConfig = false;
    // 在manifest文件中配置了configChanges的话,这里应该不为null
    if (activity.mCurrentConfig == null) {
        shouldChangeConfig = true;
    } else {
        // 如果新的config和activity当前正在运行的config一致并且override config也没有改动的话,就不要很麻烦的去调用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
                    || !REPORT_TO_ACTIVITY) {
                shouldChangeConfig = true;
            }
        }
    }
    // 是否是被显示到不同的设备上了
    if (!shouldChangeConfig && !movedToDifferentDisplay) {
        // 什么都不需要做,直接返回null
        return null;
    }

    // Propagate the configuration change to ResourcesManager and Activity.

    // ContextThemeWrappers may override the configuration for that context. We must check and
    // apply any overrides defined.
    Configuration contextThemeWrapperOverrideConfig = activity.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 ComponentCallbacks2#onConfigurationChanged(Configuration).
    // Also 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,
            displayId, movedToDifferentDisplay);

    activity.mConfigChangeFlags = 0;
    activity.mCurrentConfig = new Configuration(newConfig);

    // 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);

    if (!REPORT_TO_ACTIVITY) {
        // Not configured to report to activity.
        return configToReport;
    }

    if (movedToDifferentDisplay) {
        activity.dispatchMovedToDisplay(displayId, configToReport);
    }

    if (shouldChangeConfig) {
        activity.mCalled = false;
        activity.onConfigurationChanged(configToReport);
        if (!activity.mCalled) {
            throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
                            " did not call through to super.onConfigurationChanged()");
        }
    }

    return configToReport;
}

上述一系列方法调用,完成了对callback instanceof Activity 情况下的处理。对于其他情况,代码如下:

/**
     * Decides whether to update a component's configuration and whether to inform it.
     * @param cb The component callback to notify of configuration change.
     * @param newConfig The new configuration.
     */
    private void performConfigurationChanged(ComponentCallbacks2 cb, Configuration newConfig) {
        if (!REPORT_TO_ACTIVITY) {
            return;
        }

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

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

相关文章

网友评论

      本文标题:Android切换语言源码分析

      本文链接:https://www.haomeiwen.com/subject/njgcsqtx.html