android onConfigurationChanged 源码分析

1. 配置变更的触发

以切换深浅模式(日 -> 夜)为例,点击下拉状态栏切换深浅模式按钮,应用层触发:

java 复制代码
@RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
public boolean setNightModeActivated(boolean active) {
    if (sGlobals != null) {
        try {
            // 调用 UiModeManagerService 的 setNightModeActivated 方法
            return sGlobals.mService.setNightModeActivated(active);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    return false;
}

接着跨进程 调用 UiModeManagerService 的 setNightModeActivated 方法,这是 onConfigurationChanged 流程的起点:

java 复制代码
@Override
public boolean setNightModeActivatedForCustomMode(int modeNightCustomType, boolean active) {
    return setNightModeActivatedForModeInternal(modeNightCustomType, active);
}

@Override
public boolean setNightModeActivated(boolean active) {
    return setNightModeActivatedForModeInternal(mNightModeCustomType, active);
}

private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) {
    if (getContext().checkCallingOrSelfPermission(
            android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
            != PackageManager.PERMISSION_GRANTED) {
        Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission");
        return false;
    }
    final int user = Binder.getCallingUserHandle().getIdentifier();
    if (user != mCurrentUser && getContext().checkCallingOrSelfPermission(
            android.Manifest.permission.INTERACT_ACROSS_USERS)
            != PackageManager.PERMISSION_GRANTED) {
        Slog.e(TAG, "Target user is not current user,"
                + " INTERACT_ACROSS_USERS permission is required");
        return false;

    }
    // Store the last requested bedtime night mode state so that we don't need to notify
    // anyone if the user decides to switch to the night mode to bedtime.
    if (modeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
        mLastBedtimeRequestedNightMode = active;
    }
    if (modeCustomType != mNightModeCustomType) {
        return false;
    }
    synchronized (mLock) {
        final long ident = Binder.clearCallingIdentity();
        try {
            // 自动、自定义的主题切换
            if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
                unregisterDeviceInactiveListenerLocked();
                mOverrideNightModeOff = !active;
                mOverrideNightModeOn = active;
                mOverrideNightModeUser = mCurrentUser;
                persistNightModeOverrides(mCurrentUser);
            } else if (mNightMode == UiModeManager.MODE_NIGHT_NO
                    && active) {
                // 夜间模式
                mNightMode = UiModeManager.MODE_NIGHT_YES;
            } else if (mNightMode == UiModeManager.MODE_NIGHT_YES
                    && !active) {
                // 日间模式
                mNightMode = UiModeManager.MODE_NIGHT_NO;
            }
            // 注释1,更新Configuration
            updateConfigurationLocked();
            // 注释2,应用Configuration
            applyConfigurationExternallyLocked();
            // 为当前用户对应的Secure添加配置信息
            persistNightMode(mCurrentUser);
            return true;
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
}

首先更新了 mNightMode 的值,接着调用了 updateConfigurationLocked 方法更新内部 Configuration 对象,mConfiguration.uiMode = uiMode;

接着调用了 applyConfigurationExternallyLocked 方法,将配置变更应用到外部系统组件:

java 复制代码
private void applyConfigurationExternallyLocked() {
    if (mSetUiMode != mConfiguration.uiMode) {
        mSetUiMode = mConfiguration.uiMode;
        // load splash screen instead of screenshot
        // 清除窗口户的快照缓存
        mWindowManager.clearSnapshotCache();
        try {
            // 注释3,调用 ATMS 的 updateConfiguration 方法,更新 Configuration 到其他模块
            ActivityTaskManager.getService().updateConfiguration(mConfiguration);
        } catch (RemoteException e) {
            Slog.w(TAG, "Failure communicating with activity manager", e);
        } catch (SecurityException e) {
            Slog.e(TAG, "Activity does not have the ", e);
        }
    }
}

跨进程 调用到 ATMS 的 updateConfiguration 方法:

java 复制代码
@Override
public boolean updateConfiguration(Configuration values) {
    mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");

    synchronized (mGlobalLock) {
        if (mWindowManager == null) {
            Slog.w(TAG, "Skip updateConfiguration because mWindowManager isn't set");
            return false;
        }

        if (values == null) {
            // sentinel: fetch the current configuration from the window manager
            // 从WMS中获取当前系统默认屏幕设备对应的配置信息
            values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
        }

        mH.sendMessage(PooledLambda.obtainMessage(
                ActivityManagerInternal::updateOomLevelsForDisplay, mAmInternal,
                DEFAULT_DISPLAY));

        final long origId = Binder.clearCallingIdentity();
        try {
            if (values != null) {
                Settings.System.clearConfiguration(values);
            }
            // 更新配置信息
            updateConfigurationLocked(values, null, false, false /* persistent */,
                    UserHandle.USER_NULL, false /* deferResume */,
                    mTmpUpdateConfigurationResult);
            return mTmpUpdateConfigurationResult.changes != 0;
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }
}

继续调用 updateConfigurationLocked 执行实际的配置分发逻辑:

java 复制代码
boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
        boolean initLocale, boolean persistent, int userId, boolean deferResume,
        ActivityTaskManagerService.UpdateConfigurationResult result) {
    int changes = 0;
    boolean kept = true;

    deferWindowLayout();
    try {
        if (values != null) {
            // 更新全局配置,该方法最终会触发当前系统中所有Application对象的onConfigurationChanged方法。
            changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
        }

        if (!deferResume) {
            // 更新后确保配置和可见性,该方法会根据条件来选择触发Activity的onCreate方法或onConfigurationChanged方法。
            kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
        }
    } finally {
        continueWindowLayout();
    }

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

2. 回调所有 Application 的 onConfigurationChanged

第一阶段,通过 updateGlobalConfigurationLocked 方法回调所有 Application 对象的 onConfigurationChanged 方法:

java 复制代码
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
        boolean persistent, int userId) {

    ...

    //关键日志打印
    Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
    // TODO(multi-display): Update UsageEvents#Event to include displayId.
    mUsageStatsInternal.reportConfigurationChange(mTempConfig, mAmInternal.getCurrentUserId());

    ...

    SparseArray<WindowProcessController> pidMap = mProcessMap.getPidMap();
    for (int i = pidMap.size() - 1; i >= 0; i--) {
        final int pid = pidMap.keyAt(i);
        final WindowProcessController app = pidMap.get(pid);
        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "
                + "config %s", app.mName, mTempConfig);
        // 调用 app 的 onConfigurationChanged 方法
        // 通知每个应用进程系统配置发生了变化,最终会触发Application的onConfigurationChanged方法回调
        app.onConfigurationChanged(mTempConfig);
    }

    ...
    // Update stored global config and notify everyone about the change.
    // 通知DisplayContent系统配置发生了变化
    mRootWindowContainer.onConfigurationChanged(mTempConfig);
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);

    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    return changes;
}

遍历所有应用进程,通知配置变化,继续:

java 复制代码
@Override
public void onConfigurationChanged(Configuration newGlobalConfig) {
    super.onConfigurationChanged(newGlobalConfig);

    // If deviceId for the top-activity changed, schedule passing it to the app process.
    boolean topActivityDeviceChanged = false;
    int deviceId = getTopActivityDeviceId();
    if (deviceId != mLastTopActivityDeviceId) {
        topActivityDeviceChanged = true;
        mLastTopActivityDeviceId = deviceId;
    }

    final Configuration config = getConfiguration();
    if (mLastReportedConfiguration.equals(config) & !topActivityDeviceChanged) {
        // Nothing changed.
        if (Build.IS_DEBUGGABLE && mHasImeService) {
            // TODO (b/135719017): Temporary log for debugging IME service.
            Slog.w(TAG_CONFIGURATION, "Current config: " + config
                    + " unchanged for IME proc " + mName);
        }
        return;
    }

    if (mPauseConfigurationDispatchCount > 0) {
        mHasPendingConfigurationChange = true;
        return;
    }

    // 继续调用
    dispatchConfiguration(config);
}

继续调用 dispatchConfiguration:

java 复制代码
void dispatchConfiguration(@NonNull Configuration config) {
    mHasPendingConfigurationChange = false;
    final IApplicationThread thread = mThread;
    if (thread == null) {
        if (Build.IS_DEBUGGABLE && mHasImeService) {
            // TODO (b/135719017): Temporary log for debugging IME service.
            Slog.w(TAG_CONFIGURATION, "Unable to send config for IME proc " + mName
                    + ": no app thread");
        }
        return;
    }

    config.seq = mAtm.increaseConfigurationSeqLocked();
    setLastReportedConfiguration(config);

    // A cached process doesn't have running application components, so it is unnecessary to
    // notify the configuration change. The last-reported-configuration is still set because
    // setReportedProcState() should not write any fields that require WM lock.
    if (mRepProcState >= CACHED_CONFIG_PROC_STATE) {
        mHasCachedConfiguration = true;
        // Because there are 2 volatile accesses in setReportedProcState(): mRepProcState and
        // mHasCachedConfiguration, check again in case mRepProcState is changed but hasn't
        // read the change of mHasCachedConfiguration.
        if (mRepProcState >= CACHED_CONFIG_PROC_STATE) {
            return;
        }
    }

    onConfigurationChangePreScheduled(config);
    // (IApplicationThread, ClientTransactionItem)
    // 获取 ClientLifecycleManager 实例
    scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
            config, mLastTopActivityDeviceId));
}
java 复制代码
private void scheduleClientTransactionItem(@NonNull IApplicationThread thread,
        @NonNull ClientTransactionItem transactionItem) {
    try {
        // 获取 ClientLifecycleManager 实例
        mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
    } catch (DeadObjectException e) {
        // Expected if the process has been killed.
        Slog.w(TAG_CONFIGURATION, "Failed for dead process. ClientTransactionItem="
                + transactionItem + " owner=" + mOwner);
    } catch (Exception e) {
        Slog.e(TAG_CONFIGURATION, "Failed to schedule ClientTransactionItem="
                + transactionItem + " owner=" + mOwner, e);
    }
}

跨进程调用到 ActivityThread,会执行 ConfigurationChangeItem 的 execute 方法:

java 复制代码
@Override
public void execute(@NonNull ClientTransactionHandler client,
        @NonNull PendingTransactionActions pendingActions) {
    // 调用 ActivityThread handleConfigurationChanged
    client.handleConfigurationChanged(mConfiguration, mDeviceId);
}
java 复制代码
@Override
public void handleConfigurationChanged(Configuration config, int deviceId) {
    //调用ConfigurationController的handleConfigurationChanged方法
    mConfigurationController.handleConfigurationChanged(config);
    updateDeviceIdForNonUIContexts(deviceId);

    // These are only done to maintain @UnsupportedAppUsage and should be removed someday.
    mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi();
    mConfiguration = mConfigurationController.getConfiguration();
    mPendingConfiguration = mConfigurationController.getPendingConfiguration(
            false /* clearPending */);
}
java 复制代码
void handleConfigurationChanged(@NonNull Configuration config) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
    handleConfigurationChanged(config, null /* compat */);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}

委托给 ConfigurationController 处理配置变化,实现职责分离:

java 复制代码
void handleConfigurationChanged(@Nullable Configuration config,
        @Nullable CompatibilityInfo compat) {
    ...
    // 收集需要回调 onConfigurationChanged 的组件信息
    final ArrayList<ComponentCallbacks2> callbacks =
            mActivityThread.collectComponentCallbacks(false /* includeUiContexts */);

    freeTextLayoutCachesIfNeeded(configDiff);

    if (callbacks != null) {
        final int size = callbacks.size();
        //遍历集合,依次调用performConfigurationChanged方法
        for (int i = 0; i < size; i++) {
            ComponentCallbacks2 cb = callbacks.get(i);
            if (!equivalent) {
                performConfigurationChanged(cb, config);
            }
        }
    }
}

收集并通知所有需要配置变化回调的组件,这里不包含 UI 上下文(Activity),因为 Activity 有专门的配置变化处理机制。遍历所有组件,触发 onConfigurationChanged 回调:

java 复制代码
void performConfigurationChanged(@NonNull ComponentCallbacks2 cb,
        @NonNull Configuration newConfig) {
    // 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);
    //注释2,触发Application的onConfigurationChanged方法
    cb.onConfigurationChanged(configToReport);
}

这里会调用 Application、Service、ContentProvider 等组件的配置变化回调。

3. 回调最上层 Activity 的 onConfigurationChanged

回到 ATMS 的 updateConfigurationLocked 方法,第二阶段,继续调用 ensureConfigAndVisibilityAfterUpdate,决定 Activity 是重启还是调用 onConfigurationChanged:

java 复制代码
boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
    if (starting == null && mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
        return true;
    }
    boolean kept = true;
    // 获取当前最上层拥有焦点的 Task 对象
    final Task mainRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
    // mainRootTask is null during startup.
    if (mainRootTask != null) {
        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.
            // 获取焦点 Task 对应的 ActivityRecord
            starting = mainRootTask.topRunningActivity();
        }

        if (starting != null) {
            //注释1,调用该 ActivityRecord 的 ensureActivityConfiguration 方法
            kept = starting.ensureActivityConfiguration(changes,
                    false /* preserveWindow */);
            // And we need to make sure at this point that all other activities
            // are made visible with the correct configuration.
            // 确保其他 Activity 变成可见的时候的 Configuration 配置是正确的
            mRootWindowContainer.ensureActivitiesVisible(starting, changes,
                    !PRESERVE_WINDOWS);
        }
    }

    return kept;
}

获取当前最上层拥有焦点的 Task 对象,如果有配置变化且没有指定 starting Activity,则查找顶层运行的 Activity 来检查其配置是否需要变化,获取当前屏幕最上层 Activity。

调用该 ActivityRecord 的 ensureActivityConfiguration 方法,这里决定 Activity 是重启还是调用 onConfigurationChanged:

java 复制代码
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
        boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
    ...
    if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {// 需要重启的分支,是否声明了 uiMode
    // Aha, the activity isn't handling the change, so DIE DIE DIE.
    configChangeFlags |= changes;
    //开始冻结屏幕
    startFreezingScreenLocked(globalChanges);
    forceNewConfig = false;
    // Do not preserve window if it is freezing screen because the original window won't be
    // able to update drawn state that causes freeze timeout.
    preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
    final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
    if (hasResizeChange) {
        final boolean isDragResizing = task.isDragResizing();
        mRelaunchReason = isDragResizing ? RELAUNCH_REASON_FREE_RESIZE
                : RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
    } else {
        mRelaunchReason = RELAUNCH_REASON_NONE;
    }
    if (isRequestedOrientationChanged) {
        mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true);
    }
    if (mState == PAUSING) {
        // A little annoying: we are waiting for this activity to finish pausing. Let's not
        // do anything now, but just flag that it needs to be restarted when done pausing.
        ProtoLog.v(WM_DEBUG_CONFIGURATION,
                "Config is skipping already pausing %s", this);
        deferRelaunchUntilPaused = true;
        preserveWindowOnDeferredRelaunch = preserveWindow;
        return true;
    } else {
        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Config is relaunching %s",
                this);
        if (!mVisibleRequested) {
            ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible "
                    + "activity %s called by %s", this, Debug.getCallers(4));
        }
        //
        relaunchActivityLocked(preserveWindow);
    }

    // All done...  tell the caller we weren't able to keep this activity around.
    return false;
    }
    
    // 仅发送配置更新
    if (displayChanged) {
        scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
    } else {
        scheduleConfigurationChanged(newMergedOverrideConfig);// 发送配置变更
    }
    notifyDisplayCompatPolicyAboutConfigurationChange(
            mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
    return true;
}

未声明 uiMode 的 Activity,需要进行重启,走 relaunchActivityLocked 流程,先 destory 当前 Activity,然后重新 create。

不重启的 Activity,调用 scheduleConfigurationChanged 发送配置变更:

java 复制代码
private void scheduleConfigurationChanged(Configuration config) {
    if (!attachedToProcess()) {
        ProtoLog.w(WM_DEBUG_CONFIGURATION, "Can't report activity configuration "
                + "update - client not running, activityRecord=%s", this);
        return;
    }
    try {
        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
                + "config: %s", this, config);

        mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                ActivityConfigurationChangeItem.obtain(token, config));
    } catch (RemoteException e) {
        // If process died, whatever.
    }
}

跨进程执行 ActivityConfigurationChangeItem 的 execute 方法:

java 复制代码
@Override
public void execute(@NonNull ClientTransactionHandler client, @Nullable ActivityClientRecord r,
        @NonNull PendingTransactionActions pendingActions) {
    // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
    Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
    client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY);
    Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}

委托给 ActivityThread 处理配置变更,会调用到 ActivityThread 的 handleActivityConfigurationChanged:

java 复制代码
@Override
public void handleActivityConfigurationChanged(ActivityClientRecord r,
        @NonNull Configuration overrideConfig, int displayId) {
    handleActivityConfigurationChanged(r, overrideConfig, displayId,
            // This is the only place that uses alwaysReportChange=true. The entry point should
            // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
            // has confirmed the activity should handle the configuration instead of relaunch.
            // If Activity#onConfigurationChanged is called unexpectedly, then we can know it is
            // something wrong from server side.
            true /* alwaysReportChange */);
}
java 复制代码
void handleActivityConfigurationChanged(ActivityClientRecord r,
        @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {
    synchronized (mPendingOverrideConfigs) {
        final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token);
        if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) {
            if (DEBUG_CONFIGURATION) {
                Slog.v(TAG, "Activity has newer configuration pending so drop this"
                        + " transaction. overrideConfig=" + overrideConfig
                        + " pendingOverrideConfig=" + pendingOverrideConfig);
            }
            return;
        }
        mPendingOverrideConfigs.remove(r.token);
    }

    if (displayId == INVALID_DISPLAY) {
        // If INVALID_DISPLAY is passed assume that the activity should keep its current
        // display.
        displayId = r.activity.getDisplayId();
    }
    final boolean movedToDifferentDisplay = isDifferentDisplay(
            r.activity.getDisplayId(), displayId);
    if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
            && !movedToDifferentDisplay) {
        if (DEBUG_CONFIGURATION) {
            Slog.v(TAG, "Activity already handled newer configuration so drop this"
                    + " transaction. overrideConfig=" + overrideConfig + " r.overrideConfig="
                    + r.overrideConfig);
        }
        return;
    }

    // Perform updates.
    r.overrideConfig = overrideConfig;
    final ViewRootImpl viewRoot = r.activity.mDecor != null
        ? r.activity.mDecor.getViewRootImpl() : null;

    if (DEBUG_CONFIGURATION) {
        Slog.v(TAG, "Handle activity config changed, activity:"
                + r.activityInfo.name + ", displayId=" + r.activity.getDisplayId()
                + (movedToDifferentDisplay ? (", newDisplayId=" + displayId) : "")
                + ", config=" + overrideConfig);
    }
    final Configuration reportedConfig = performConfigurationChangedForActivity(r,
            mConfigurationController.getCompatConfiguration(),
            movedToDifferentDisplay ? displayId : r.activity.getDisplayId(),
            alwaysReportChange);
    // Notify the ViewRootImpl instance about configuration changes. It may have initiated this
    // update to make sure that resources are updated before updating itself.
    if (viewRoot != null) {
        if (movedToDifferentDisplay) {
            viewRoot.onMovedToDisplay(displayId, reportedConfig);
        }
        viewRoot.updateConfiguration(displayId);
    }
    mSomeActivitiesChanged = true;
}

继续调用 performConfigurationChangedForActivity,这里会决定是否需要更新资源、是否需要回调 Activity.onConfigurationChanged():

java 复制代码
private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
        Configuration newBaseConfig, int displayId, boolean alwaysReportChange) {
    r.tmpConfig.setTo(newBaseConfig);
    if (r.overrideConfig != null) {
        r.tmpConfig.updateFrom(r.overrideConfig);
    }
    final Configuration reportedConfig = performActivityConfigurationChanged(r,
            r.tmpConfig, r.overrideConfig, displayId, alwaysReportChange);
    freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
    return reportedConfig;
}
java 复制代码
private Configuration performActivityConfigurationChanged(ActivityClientRecord r,
        Configuration newConfig, Configuration amOverrideConfig, int displayId,
        boolean alwaysReportChange) {
    final Activity activity = r.activity;
    final IBinder activityToken = activity.getActivityToken();

    // WindowConfiguration differences aren't considered as public, check it separately.
    // multi-window / pip mode changes, if any, should be sent before the configuration
    // change callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
    handleWindowingModeChangeIfNeeded(r, newConfig);

    final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
            displayId);
    final Configuration currentResConfig = activity.getResources().getConfiguration();
    final int diff = currentResConfig.diffPublicOnly(newConfig);
    final boolean hasPublicResConfigChange = diff != 0;
    // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
    // ResourcesImpl constructions.
    final boolean shouldUpdateResources = hasPublicResConfigChange
            || shouldUpdateResources(activityToken, currentResConfig, newConfig,
            amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);

    // TODO(b/274944389): remove once a longer-term solution is implemented.
    boolean skipActivityRelaunchWhenDocking = activity.getResources().getBoolean(
            R.bool.config_skipActivityRelaunchWhenDocking);
    int handledConfigChanges = activity.mActivityInfo.getRealConfigChanged();
    if (skipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(activity.mCurrentConfig,
            newConfig)) {
        // If we're not relaunching this activity when docking, we should send the configuration
        // changed event. Pretend as if the activity is handling uiMode config changes in its
        // manifest so that we'll report any dock changes.
        handledConfigChanges |= ActivityInfo.CONFIG_UI_MODE;
    }

    final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
            r.mSizeConfigurations, handledConfigChanges, alwaysReportChange);
    // Nothing significant, don't proceed with updating and reporting.
    if (!shouldUpdateResources && !shouldReportChange) {
        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);

    // 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 (movedToDifferentDisplay) {
        activity.dispatchMovedToDisplay(displayId, configToReport);
    }

    activity.mConfigChangeFlags = 0;
    if (shouldReportChange) {
        activity.mCalled = false;
        activity.mCurrentConfig = new Configuration(newConfig);
        activity.onConfigurationChanged(configToReport);// 不重建,回调 onConfigurationChanged
        if (!activity.mCalled) {
            throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
                            " did not call through to super.onConfigurationChanged()");
        }
    }
    mConfigurationChangedListenerController
            .dispatchOnConfigurationChanged(activity.getActivityToken());

    return configToReport;
}

此方法最终会调用 Activity 的 onConfigurationChanged 方法

4. 回调后台 Activity 的 onConfigurationChanged

发生配置变更后,当处于后台的进程回到前台,会调用到:ActivityConfigurationChangeItem 的 execute 方法。执行时机在 TopResumedActivityChangeItem 之后,在 ResumeActivityItem 之前。

Activity 从后台回到前台,调用到 TaskFragment 的 resumeTopActivity 方法:

java 复制代码
final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
        boolean skipPause) {
    ...
    if (next.attachedToProcess()) {// Activity 已启动
        ...
        // TopResumedActivityChangeItem 流程
        next.setState(RESUMED, "resumeTopActivity");
        ...
        if (shouldBeVisible(next)) {
            // 配置变更
            notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
                    false /* deferResume */);
        }
        ...
        // ResumeActivityItem
        final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
                next.token, topProcessState, dc.isNextTransitionForward(),
                next.shouldSendCompatFakeFocus());
        ...
    }
}

调用 RootWindowContainer 的 ensureVisibilityAndConfig 方法,发送配置变更:

java 复制代码
boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId, boolean deferResume) {
    // First ensure visibility without updating the config just yet. We need this to know what
    // activities are affecting configuration now.
    // Passing null here for 'starting' param value, so that visibility of actual starting
    // activity will be properly updated.
    ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
            false /* preserveWindows */, false /* notifyClients */);

    if (displayId == INVALID_DISPLAY) {
        // The caller didn't provide a valid display id, skip updating config.
        return true;
    }

    // Force-update the orientation from the WindowManager, since we need the true configuration
    // to send to the client now.
    final DisplayContent displayContent = getDisplayContent(displayId);
    Configuration config = null;
    if (displayContent != null) {
        config = displayContent.updateOrientation(starting, true /* forceUpdate */);
    }
    // Visibilities may change so let the starting activity have a chance to report. Can't do it
    // when visibility is changed in each AppWindowToken because it may trigger wrong
    // configuration push because the visibility of some activities may not be updated yet.
    if (starting != null) {
        starting.reportDescendantOrientationChangeIfNeeded();
    }

    if (displayContent != null) {
        // Update the configuration of the activities on the display.
        // 更新显示设备上 Activity 的配置
        return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
                deferResume, null /* result */);
    } else {
        return true;
    }
}

继续调用 DisplayContent 的 updateDisplayOverrideConfigurationLocked 方法:

java 复制代码
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
        ActivityRecord starting, boolean deferResume,
        ActivityTaskManagerService.UpdateConfigurationResult result) {

    
    try {
        ....
        if (!deferResume) {
            // 确保配置更新后的可见性和 Activity 状态
            kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
        }
    } 
    ...
    return kept;
}

继续调用 ATMS 的 ensureConfigAndVisibilityAfterUpdate 方法:

java 复制代码
boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
    
    // 获取当前最上层拥有焦点的根任务
    final Task mainRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();

    // 系统启动期间 mainRootTask 可能为 null、
    if (mainRootTask != null) {
        // 如果有配置变化但没有指定目标 Activity
        if (changes != 0 && starting == null) {
            // 自动查找需要处理配置变化的顶层 Activity、
            starting = mainRootTask.topRunningActivity();
        }

        if (starting != null) {
            // 调用目标 Activity 的配置确保方法
            kept = starting.ensureActivityConfiguration(changes,
                    false /* preserveWindow */);
            mRootWindowContainer.ensureActivitiesVisible(starting, changes,
                    !PRESERVE_WINDOWS);
        }
    }

    // 表示目标 Activity 是否被保留(true=保留,false=重建)
    return kept;
}

继续调用 ActivityRecord 的 ensureActivityConfiguration 方法:

java 复制代码
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
        boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
    if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
        relaunchActivityLocked(preserveWindow);
    }
    ...
    if (displayChanged) {
        scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
    } else {
        scheduleConfigurationChanged(newMergedOverrideConfig);
    }

配置变更,Activity 不重启,调用 scheduleConfigurationChanged 方法,接着会跨进程调用 ActivityConfigurationChangeItem 的 execute 方法,和之前流程一样了。

相关推荐
锅拌饭4 小时前
RecyclerView布局绘制优化-OkLayoutInflater
android·面试
我是哪吒4 小时前
分布式微服务系统架构第169集:1万~10万QPS的查当前订单列表
后端·面试·github
庚云4 小时前
前端项目中 .env 文件的原理和实现
前端·面试
jctech4 小时前
ComboLite插件化框架未来开发计划
android·开源
就是帅我不改5 小时前
面试官:单点登录怎么实现?我:你猜我头发怎么没的!
后端·面试·程序员
程序员清风5 小时前
贝壳三面:RocketMQ和KAFKA的零拷贝有什么区别?
java·后端·面试
青鱼入云5 小时前
【面试场景题】1GB 大小HashMap在put时遇到扩容的过程
java·数据结构·面试
IT毕设梦工厂6 小时前
大数据毕业设计选题推荐-基于大数据的电信客户流失数据分析系统-Hadoop-Spark-数据可视化-BigData
大数据·hadoop·spark·毕业设计·源码·数据可视化·bigdata