Configuration变更时更新应用程序配置

基于Android U,只保留了关键调用步骤,略去了细节。

当设备配置发生变更时,系统会调用ATMS的updateConfiguration()方法,来通知ATMS处理configuration change事件。时序图如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
public boolean updateConfiguration(Configuration values) {
...
synchronized (mGlobalLock) {
...
try {
...
updateConfigurationLocked(values, null, false, false /* persistent */,
UserHandle.USER_NULL, false /* deferResume */,
mTmpUpdateConfigurationResult); // 1
return mTmpUpdateConfigurationResult.changes != 0;
} finally {
...
}
}
}

注释1处调用updateConfigurationLocked()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
boolean initLocale, boolean persistent, int userId, boolean deferResume,
ActivityTaskManagerService.UpdateConfigurationResult result) {
...
try {
if (values != null) {
changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId); // 1
}

if (!deferResume) {
kept = ensureConfigAndVisibilityAfterUpdate(starting, changes); // 2
}
} finally {
...
}
...
return kept;
}

注释1处更新当前配置;

注释2处确保给定的Activity使用的是当前配置,如果返回true表示Activity未被重启,否则让该Activity destroyed以适配当前配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
...
if (mainRootTask != null) {
...
if (starting != null) {
kept = starting.ensureActivityConfiguration(changes,
false /* preserveWindow */); // 1
...
}
}

return kept;
}

注释1处调用ActivityRecord#ensureActivityConfiguration()真正完成Activity配置更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
return ensureActivityConfiguration(globalChanges, preserveWindow,
false /* ignoreVisibility */, false /* isRequestedOrientationChanged */);
}

boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
// 这里省略了一些return ture的分支
// 如果马上会再次调用updateConfiguration(),则忽略本次修改,交由下次处理,节省时间
// 如果当前Activity已经finish,则忽略
// 如果Activity的状态是DESTROYED,则忽略
// 如果Activity不可见。则忽略
...
// 如果新旧配置相同,则忽略
mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
if (getConfiguration().equals(mTmpConfig) && !forceNewConfig && !displayChanged) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
+ "unchanged in %s", this);
return true;
}

// 计算哪些配置值发生了改变
final int changes = getConfigurationChanges(mTmpConfig);

// Update last reported values.
final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration();

setLastReportedConfiguration(getProcessGlobalConfiguration(), newMergedOverrideConfig);

// Activity状态是INITIALIZING,则忽略
...

if (changes == 0 && !forceNewConfig) {
...
if (displayChanged) {
scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
} else {
scheduleConfigurationChanged(newMergedOverrideConfig);
}
notifyDisplayCompatPolicyAboutConfigurationChange(
mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
return true;
}
...
boolean shouldRelaunchLocked = shouldRelaunchLocked(changes, mTmpConfig); // 1
...
if (shouldRelaunchLocked || forceNewConfig) { // 重启Activity
...
if (mState == PAUSING) {
...
// 如果当前Activity处于PAUSING状态,则标记其需要重启,等到PAUSING后reLaunch
deferRelaunchUntilPaused = true;
preserveWindowOnDeferredRelaunch = preserveWindow;
return true;
} else {
...
relaunchActivityLocked(preserveWindow);
}

// All done... tell the caller we weren't able to keep this activity around.
return false;
}

// Activity可以自己处理配置变更走这里
if (displayChanged) {
scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
} else {
scheduleConfigurationChanged(newMergedOverrideConfig);
}
...
return true;
}

决定是否需要reLaunch的关键参数是shouldRelaunchLockedforceNewConfig,任一值为true,就会重启Activity,而不会调用Activity#onConfigurationChanged()

注释1处shouldRelaunchLocked()会通过对比事件是否在Activity自己可以处理的范围内来决定是否reLaunch,其中包括AndroidManifest.xml中配置的android:configChanges属性。

  1. 重启Activity分支

    执行ActivityRecord#relaunchActivityLocked(),经过层层调用,最终调用到ActivityThread#handleRelaunchActivityInner()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    frameworks/base/core/java/android/app/ActivityThread.java
    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
    List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
    PendingTransactionActions pendingActions, boolean startsNotResumed,
    Configuration overrideConfig, String reason) {
    ...
    handleDestroyActivity(r, false, configChanges, true, reason);
    ...
    handleLaunchActivity(r, pendingActions, mLastReportedDeviceId, customIntent);
    }

    handleRelaunchActivityInner()中,先调用handleDestroyActivity()销毁当前Activity,然后调用handleLaunchActivity()重启Activity。Activity有一个回调方法onRetainNonConfigurationInstance(),当设备信息变更时,会保存该方法返回的Object,之后可以在重启的Activity中通过getLastNonConfigurationInstance()获取该Object。

  2. 不重启Activity分支

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
    boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
    boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
    ...
    // Activity可以自己处理配置变更走这里
    if (displayChanged) {
    scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
    } else {
    scheduleConfigurationChanged(newMergedOverrideConfig);
    }
    ...
    }

    这两个分支最终都会调用ActivityThread#handleActivityConfigurationChanged()方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    frameworks/base/core/java/android/app/ActivityThread.java
    void handleActivityConfigurationChanged(ActivityClientRecord r,
    @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {
    ...
    final Configuration reportedConfig = performConfigurationChangedForActivity(r,
    mConfigurationController.getCompatConfiguration(),
    movedToDifferentDisplay ? displayId : r.activity.getDisplayId(),
    alwaysReportChange);
    ...
    }

    调用performConfigurationChangedForActivity()。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    frameworks/base/core/java/android/app/ActivityThread.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;
    }

    调用performActivityConfigurationChanged()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    private Configuration performActivityConfigurationChanged(ActivityClientRecord r,
    Configuration newConfig, Configuration amOverrideConfig, int displayId,
    boolean alwaysReportChange) {
    final Activity activity = r.activity;
    ...
    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);
    final boolean shouldReportChange = shouldReportChange(
    activity.mCurrentConfig, newConfig, r.mSizeConfigurations,
    activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);
    // Nothing significant, don't proceed with updating and reporting.
    if (!shouldUpdateResources && !shouldReportChange) {
    return null;
    }
    ...
    final Configuration configToReport = createNewConfigAndUpdateIfNotNull(newConfig,
    contextThemeWrapperOverrideConfig);
    ...
    activity.mConfigChangeFlags = 0;
    if (shouldReportChange) {
    activity.mCalled = false;
    activity.mCurrentConfig = new Configuration(newConfig);
    activity.onConfigurationChanged(configToReport); // 1
    if (!activity.mCalled) {
    throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
    " did not call through to super.onConfigurationChanged()");
    }
    }

    return configToReport;
    }

    注释1处调用Activity.onConfigurationChanged()

总结:设备配置发生变更时,系统会根据一系列条件决定是重启Activity,还是调用Activity.onConfigurationChanged()


Configuration变更时更新应用程序配置
https://citrus-maxima.github.io/2024/03/10/Configuration变更时更新应用程序配置/
作者
柚子树
发布于
2024年3月10日
许可协议