Configuration变更时更新应用程序配置
基于Android U,只保留了关键调用步骤,略去了细节。
当设备配置发生变更时,系统会调用ATMS的updateConfiguration()
方法,来通知ATMS处理configuration change事件。时序图如下。
1 |
|
注释1处调用updateConfigurationLocked()
。
1 |
|
注释1处更新当前配置;
注释2处确保给定的Activity使用的是当前配置,如果返回true表示Activity未被重启,否则让该Activity destroyed以适配当前配置。
1 |
|
注释1处调用ActivityRecord#ensureActivityConfiguration()
真正完成Activity配置更新。
1 |
|
决定是否需要reLaunch的关键参数是shouldRelaunchLocked
和forceNewConfig
,任一值为true,就会重启Activity,而不会调用Activity#onConfigurationChanged()
。
注释1处shouldRelaunchLocked()
会通过对比事件是否在Activity自己可以处理的范围内来决定是否reLaunch,其中包括AndroidManifest.xml
中配置的android:configChanges
属性。
重启Activity分支
执行
ActivityRecord#relaunchActivityLocked()
,经过层层调用,最终调用到ActivityThread#handleRelaunchActivityInner()
。1
2
3
4
5
6
7
8
9
10frameworks/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。不重启Activity分支
1
2
3
4
5
6
7
8
9
10
11
12frameworks/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
10frameworks/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
12frameworks/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
37private 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()
。