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 方法,和之前流程一样了。