CONFIG_UI_MODE 切换如何顺畅过渡 可以学习基于旋转屏动画实现原理。
主要涉及 framework/base/services/java/com/android/server/wm/ActivityTaskManagerService.java ActivityTaskManagerService.java
frameworks/base/services/core/java/com/android/server/wm/ScreenRotationAnimation.java ScreenRotationAnimation.java
切换config都会走到这里。 #ActivityTaskManagerService.updateGlobalConfigurationLocked()
/** Update default (global) configuration and notify listeners about changes. */
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId, boolean deferResume) {
final DisplayContent defaultDisplay =
mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
mTempConfig.setTo(getGlobalConfiguration());
//理论上要解决切换暗色模式config 卡顿问题,可以这里处理
//这里changes可以判断是不是UIMode的config 如果(changes & ActivityInfo.CONFIG_UI_MODE)!= 0那么就是uimode的切换,
//如果uimode切换我们就调用冻屏,(注意:原生代码这里是没有的)
//defaultDisplay.getDisplayRotation().cancelSeamlessRotation()保证一个旋转屏动画正常执行,不会被新的DisplayContent的config中断
//开启截图冻屏
//mWinowManager.startFreezingDisplay(0,0, defaultDisplay)原生走旋转屏动画也会走到这里,WMS里调用startFreezingDisplay。
//给DisplayContent设置ScreenRotationAnimation,这个ScreenRotationAnimation会从SF抽取一帧,然后做动画。
//旋转屏动画具体参考https://www.jianshu.com/p/db47547605fb
//https://blog.csdn.net/qq_24604069/article/details/109694113
//https://funnyray.plus/2020/04/17/WMS%20Orientation&Rotation/
final int changes = mTempConfig.updateFrom(values);
if (changes == 0) {
// Since calling to Activity.setRequestedOrientation leads to freezing the window with
// setting WindowManagerService.mWaitingForConfig to true, it is important that we call
// performDisplayOverrideConfigUpdate in order to send the new display configuration
// (even if there are no actual changes) to unfreeze the window.
defaultDisplay.performDisplayOverrideConfigUpdate(values, deferResume);
return 0;
}
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
"Updating global configuration to: " + values);
writeConfigurationChanged(changes);
FrameworkStatsLog.write(FrameworkStatsLog.RESOURCE_CONFIGURATION_CHANGED,
values.colorMode,
values.densityDpi,
values.fontScale,
values.hardKeyboardHidden,
values.keyboard,
values.keyboardHidden,
values.mcc,
values.mnc,
values.navigation,
values.navigationHidden,
values.orientation,
values.screenHeightDp,
values.screenLayout,
values.screenWidthDp,
values.smallestScreenWidthDp,
values.touchscreen,
values.uiMode);
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);
final Message m = PooledLambda.obtainMessage(
ActivityTaskManagerService::sendLocaleToMountDaemonMsg, this,
locales.get(bestLocaleIndex));
mH.sendMessage(m);
}
mTempConfig.seq = increaseConfigurationSeqLocked();
// Update stored global config and notify everyone about the change.
mRootWindowContainer.onConfigurationChanged(mTempConfig);
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
// TODO(multi-display): Update UsageEvents#Event to include displayId.
mUsageStatsInternal.reportConfigurationChange(mTempConfig, mAmInternal.getCurrentUserId());
// TODO: If our config changes, should we auto dismiss any currently showing dialogs?
updateShouldShowDialogsLocked(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)) {
final Message msg = PooledLambda.obtainMessage(
ActivityTaskManagerService::sendPutConfigurationForUserMsg,
this, userId, configCopy);
mH.sendMessage(msg);
}
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);
if (DEBUG_CONFIGURATION) {
Slog.v(TAG_CONFIGURATION, "Update process config of "
+ app.mName + " to new config " + configCopy);
}
app.onConfigurationChanged(configCopy);
}
final Message msg = PooledLambda.obtainMessage(
ActivityManagerInternal::broadcastGlobalConfigurationChanged,
mAmInternal, changes, initLocale);
mH.sendMessage(msg);
// Override configuration of the default display duplicates global config, so we need to
// update it also. This will also notify WindowManager about changes.
defaultDisplay.performDisplayOverrideConfigUpdate(mRootWindowContainer.getConfiguration(),
deferResume);
return changes;
}