忽然有一天,我想要做一件事:去代码中去验证那些曾经被"灌输"的理论。
-- 服装学院的IT男【Android 13源码分析】
上一篇看到旋转的最初的一些处理,这个时候屏幕上有一个截图的图层挡住。
本篇会设计到屏幕旋转后,新的 Configuration 生成及派发的流程,但是 Configuration 不是重点。
但是需要提一个点,Configuration 是存储设置的,但根据代码分析发现不是全局唯一的,各个进程,或者各个容器容器类,都会有自己的 Configuration 。正常来说是应该和手机设备的 Configuration 一致,但是也有不一样的情况,比如单独对应用设置语言的时候。 关于 Configuration 具体的内容还是很多的,本篇可以以黑盒的概念去阅读,不影响主流程分析,后续会想洗单独介绍一下 Configuration 。
本篇的主要调用链如下:
arduino
DisplayContent::sendNewConfiguration
DisplayContent::updateDisplayOverrideConfigurationLocked
Configuration::init
DisplayContent::computeScreenConfiguration -- 计算出最新的Configuration
DisplayContent::updateDisplayAndOrientation -- 返回新的DisplayInfo
DisplayContent::updateDisplayOverrideConfigurationLocked -- 更新全局Configuration(重点方法)
ActivityTaskManagerService::updateGlobalConfigurationLocked -- 更新Configuration
ConfigurationContainer::getGlobalConfiguration -- 拿到之前的Configuration
Configuration::updateFrom -- 更新到Configuration
WindowProcessController::onConfigurationChanged -- 进程更新Configuration
WindowProcessController::updateConfiguration
WindowProcessController::dispatchConfiguration
ConfigurationChangeItem::init -- 构建执行,后续由应用进程处理
WindowContainer::onConfigurationChanged -- 层级树更新Configuration
ConfigurationContainer::onConfigurationChanged
RootWindowContainer::dispatchConfigurationToChild
DisplayContent::performDisplayOverrideConfigUpdate
.....忽略
ScreenRotationAnimation::setRotation -- 移动截图Surface
WindowState::onResize -- 最终会到窗口的 onResize
ActivityTaskManagerService::ensureConfigAndVisibilityAfterUpdate -- 确保应用正确的显示
ActivityRecord::ensureActivityConfiguration
ActivityRecord::shouldRelaunchLocked -- 判断是否重启
ActivityRecord::startFreezingScreenLocked -- 开始冻屏
ActivityRecord::relaunchActivityLocked -- 重启Activity
ActivityRelaunchItem::init -- 重启事务
RootWindowContainer::ensureActivitiesVisible -- 常见ensureActivitiesVisible流程
ActivityTaskManagerService::continueWindowLayout -- 触发一次layout
前面说了,各个进程,容器的 Configuration 不完全一致,屏幕旋转后计算出一个新的设备 Configuration 后,会传递到各个进程,容器树的成员再根据自己的状况,结合传递过来新的 Configuration 计算更新自己的 Configuration 。然后各个地方都有了自己最新的 Configuration 。
1. 准备新的 Configuration
本篇接着上篇末尾的 DisplayContent::sendNewConfiguration 继续分析
arduino
# DisplayContent
void sendNewConfiguration() {
// 这个一般正常都是true
if (!isReady()) {
return;
}
// 接上篇末尾的分析,在DisplayRotation::continueRotation 设置为了false
if (mDisplayRotation.isWaitingForRemoteRotation()) {
return;
}
// 更新Configuration
final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
if (configUpdated) {
return;
}
// 正常这里都是有配置改变的,所以不会执行到后面的逻辑
......
}
scss
# DisplayContent
boolean updateDisplayOverrideConfigurationLocked() {
......
// 创建一个新的Configuration对象
Configuration values = new Configuration();
// 计算配置
computeScreenConfiguration(values);
mAtmService.mH.sendMessage(PooledLambda.obtainMessage(
ActivityManagerInternal::updateOomLevelsForDisplay, mAtmService.mAmInternal,
mDisplayId));
Settings.System.clearConfiguration(values);
// 更新
updateDisplayOverrideConfigurationLocked(values, null /* starting */,
false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
}
这里先创建一个新的 Configuration ,这个时候他是空的,后面肯定会进行数据填充,填充的依据就是基于设备之前的配置,然后再把当前操作的修改进行更新,比如当前分析的是屏幕旋转, 那最终的操作无非就基于之前的配置,然后把最新的旋转角度修改过去,就得到了目前最新的设备配置,然后在应用到手机上。
1.1 计算 Configuration
arduino
# DisplayContent
void computeScreenConfiguration(Configuration config) {
// 重点* 更新屏幕方向后,获取最新的DisplayInfo
final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config);
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
// 宽高设置到临时变量
mTmpRect.set(0, 0, dw, dh);
// 开始把数据更新到Configuration中
config.windowConfiguration.setBounds(mTmpRect);
config.windowConfiguration.setMaxBounds(mTmpRect);
......// 忽略一堆的config设置
config.navigation = Configuration.NAVIGATION_NONAV;
......// 忽略一堆的config设置
mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
}
这里会根据之前没有改变的配置项,已经修改后最新的配置项,把最新的配置信息填充到参数 config 中,经过这一步后,config 下的属性就是最新的了
看一下 DisplayInfo 的更新。
1.1.1 更新 DisplayInfo
ini
# DisplayContent
private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) {
// Use the effective "visual" dimensions based on current rotation
// 获取到最新的旋转
final int rotation = getRotation();
// 如果旋转了 90或者270度,说明横竖屏切换了,则屏幕宽高需要交互
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
// Update application display metrics.
// 刘海屏,圆角的一些计算
final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
displayCutout);
final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation,
displayCutout);
// 根据最新的的配置到mDisplayInfo下
mDisplayInfo.rotation = rotation;
mDisplayInfo.logicalWidth = dw;
mDisplayInfo.logicalHeight = dh;
......// 忽略其他mDisplayInfo的设置
// DisplayInfo 数据改变了,做响应处理
onDisplayInfoChanged();
return mDisplayInfo;
}
DisplayContent::updateDisplayAndOrientation方法看着就是根据最新的角度更新了 DisplayInfo 的信息, 经过这个方法后 DisplayInfo 内存储的就是最新的屏幕信息了,在这步之前,还是旋转前的信息
csharp
# DisplayContent
@Rotation
int getRotation() {
return mDisplayRotation.getRotation();
}
最开始的时候就知道 最新的角度被保存在了 DisplayRotation 下的 mRotation 中,这个方法就是获取的。
在这一小节先是 更新了 DisplayInfo ,然后把配置更新到了新创建的 Configuration 中。
2. 更新全局配置,并做UI处理
经过前面的处理,现在有了一个新的 Configuration ,就需要更新到设备上,注意这个方法是带参数的,和 1.1 开始分析的不带参数是同名函数。
这个带参数的方法,是全局配置改变都会执行的,除了横竖屏,比如设置语言,也会执行到这边。
不过debug发现设置语言传进来的参数 values 是个null。 当前旋转场景根据前面的分析肯定是有值的。
ini
# DisplayContent
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
ActivityRecord starting, boolean deferResume,
ActivityTaskManagerService.UpdateConfigurationResult result) {
// 标志位,看看哪些设置改变了
int changes = 0;
boolean kept = true;
mAtmService.deferWindowLayout();
try {
if (values != null) {
if (mDisplayId == DEFAULT_DISPLAY) {
// Override configuration of the default display duplicates global config, so
// we're calling global config update instead for default display. It will also
// apply the correct override config.
// 1. 更改全局配置
changes = mAtmService.updateGlobalConfigurationLocked(values,
false /* initLocale */, false /* persistent */,
UserHandle.USER_NULL /* userId */);
} else {
changes = performDisplayOverrideConfigUpdate(values);
}
}
if (!deferResume) {
// 2. 确保设置更新后正确的显示
kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
// 3. 执行一次layout
mAtmService.continueWindowLayout();
}
if (result != null) {
result.changes = changes;
result.activityRelaunched = !kept;
}
return kept;
}
这里有2条主线:
-
- 更新全局的配置
-
- 配置更新后,做相应的UI处理
2.1 更新配置
java
# ActivityTaskManagerService
// 临时存储Configuration
private final Configuration mTempConfig = new Configuration();
// 更新默认的全局配置并通知到各个监听
/** Update default (global) configuration and notify listeners about changes. */
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId) {
// 获取到现在的设置,保存到临时变量中
mTempConfig.setTo(getGlobalConfiguration());
// 重点* 1. updateFrom 方法会对把传进来的Configuration 更新到 mTempConfig中
// 并返回更新了哪些设置项
final int changes = mTempConfig.updateFrom(values);
if (changes == 0) {
// 如果没有更新,则返回
return 0;
}
// 日志
ProtoLog.i(WM_DEBUG_CONFIGURATION, "Updating global configuration "
+ "to: %s", values);
......
// 通知进程Configuration改变了
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);
// 重点* 2. 通知进程更新Configuration
app.onConfigurationChanged(mTempConfig);
}
......
// 重点* 3. 层级树更新Configuration
mRootWindowContainer.onConfigurationChanged(mTempConfig);
return changes;
}
这个方法是 Configuration 修改的核心方法,分为以下3步:
-
- 得到一个新的 Configuration ,并记录那些设置项发生了改变
-
- 把新的 Configuration 传递到各个应用
-
- 把新的 Configuration 传递到层级树
2.1.1 获取到新的 Configuration
这是三步的第一步,先要得到一个最新的 Configuration 对象,这个对象后续也将传递给各个进程和窗口树使用。 首先是通过 getGlobalConfiguration 方法获取到了当前使用的 Configuration 对象
csharp
# ActivityTaskManagerService
Configuration getGlobalConfiguration() {
// Return default configuration before mRootWindowContainer initialized, which happens
// while initializing process record for system, see {@link
// ActivityManagerService#setSystemProcess}.
return mRootWindowContainer != null ? mRootWindowContainer.getConfiguration()
: new Configuration();
}
// 正常情况 mRootWindowContainer 是不为空的,这边返回的其实就是设备之前的 Configuration 。 getConfiguration 方法是 定义在 WindowContainer 父类 ConfigurationContainer 下的一个方法
csharp
# ConfigurationContainer
/**
* Contains full configuration applied to this configuration container. Corresponds to full
* parent's config with applied {@link #mResolvedOverrideConfiguration}.
*/
private Configuration mFullConfiguration = new Configuration();
@NonNull
public Configuration getConfiguration() {
return mFullConfiguration;
}
ConfigurationContainer 下定义了好几个 Configuration 对象,都有各自的意义 。 这里知道 mFullConfiguration 才是当前容器配置保存的变量就好了。
总之,就是获取到了设置之前的 Configuration 对象,然后保存到临时变量 mTempConfig 中。
然后执行 "final int changes = mTempConfig.updateFrom(values);" 看一下 Configuration::updateFrom 方法
less
# Configuration
/**
* Copies the fields from delta into this Configuration object, keeping
* track of which ones have changed. Any undefined fields in {@code delta}
* are ignored and not copied in to the current Configuration.
*
* @return a bit mask of the changed fields, as per {@link #diff}
*/
public @Config int updateFrom(@NonNull Configuration delta) {
// 定义个flag,看看哪些设置项改变了
int changed = 0;
......
if (delta.orientation != ORIENTATION_UNDEFINED
&& orientation != delta.orientation) {
changed |= ActivityInfo.CONFIG_ORIENTATION;
orientation = delta.orientation;
}
return changed;
......
}
看注释就是把 delta 改变的复制到当前的 Configuration 对象中,并且记录那些改变了。 这个方法代码很长,是把 Configuration 下的每个属性都做了判断,但是当前只关心屏幕方法,所以只保留了一小块代码。 如果新的 Configuration 屏幕方向和当前的不一样,则说明改变了,就更新过来,并且记录到 changed 中,最终这个方法返回 changed。
mTempConfig 现在代表着最新的 Configuration ,稍后会给应用进程,以及层级树使用。
2.1.2 进程更新Configuration
WindowProcessController 的父类也是 ConfigurationContainer
scss
# WindowProcessController
@Override
public void onConfigurationChanged(Configuration newGlobalConfig) {
// 执行父类的方法更新 Configuration
super.onConfigurationChanged(newGlobalConfig);
updateConfiguration();
}
private void updateConfiguration() {
final Configuration config = getConfiguration();
......
dispatchConfiguration(config);
}
void dispatchConfiguration(Configuration config) {
......
// 日志
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
config);
try {
config.seq = mAtm.increaseConfigurationSeqLocked();
// 构建执行 ConfigurationChangeItem
mAtm.getLifecycleManager().scheduleTransaction(mThread,
ConfigurationChangeItem.obtain(config));
setLastReportedConfiguration(config);
} catch (Exception e) {
Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
}
}
看一眼这个 ConfigurationChangeItem
typescript
# ConfigurationChangeItem
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
client.handleConfigurationChanged(mConfiguration);
}
这边的逻辑就是把新的 Configuration 发生到各个进程,后面的逻辑就在应用进程的 ActivityThread 处理了。
2.1.3 层级树更新 Configuration
在 2.1.1 小节获取 Configuration 的时候看到最终其实是从 ConfigurationContainer 下取的 mFullConfiguration 变量,由此也能知道原来每个容器类都有自己的一个 Configuration 变量。
容器更新自己的 Configuration 流程是一个从上往下的流程,从 RootWindowContainer 开始接收一个新的 Configuration ,然后不断派发到层级树到各个子容器,子容器根据父容器传递过来的这个新的 Configuration 再结合自己的情况更新出自己的 Configuration 。
这部分的逻辑其实应该是 Configuration 专题的,所以不详细介绍各个容器是如何更新自己 Configuration 的,但是当前场景这条调用链有2个需要注意的点:
-
- 再次调用 setRotation 来设置截图图层的位置
-
- 通知窗口进行 resize 操作。
2.1.3.1 setRotation
ScreenRotationAnimation::setRotation 方法之前看过一次,但是由于当时新的旋转角度没更新到 DisplayInfo 中,所以相当于没有执行什么,但是这次 DisplayContent::sendNewConfiguration 触发的调用连前面看 DisplayInfo 已经更新了,所以这次这次触发 ScreenRotationAnimation::setRotation 方法时,是会真正移动 Surface 位置的。
这次的调用链如下:
less
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.ScreenRotationAnimation.setRotation(ScreenRotationAnimation.java:326)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayContent.applyRotation(DisplayContent.java:2025)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayContent.applyRotationAndFinishFixedRotation(DisplayContent.java:6008)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayContent.onRequestedOverrideConfigurationChanged(DisplayContent.java:5978)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayContent.performDisplayOverrideConfigUpdate(DisplayContent.java:5948)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.RootWindowContainer.dispatchConfigurationToChild(RootWindowContainer.java:640)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.RootWindowContainer.dispatchConfigurationToChild(RootWindowContainer.java:169)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.ConfigurationContainer.onConfigurationChanged(ConfigurationContainer.java:152)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.WindowContainer.onConfigurationChanged(WindowContainer.java:519)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.ActivityTaskManagerService.updateGlobalConfigurationLocked(ActivityTaskManagerService.java:4360)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayContent.updateDisplayOverrideConfigurationLocked(DisplayContent.java:5918)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayContent.updateDisplayOverrideConfigurationLocked(DisplayContent.java:5894)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayContent.sendNewConfiguration(DisplayContent.java:1468)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayRotation.continueRotation(DisplayRotation.java:607)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayRotation.-$$Nest$mcontinueRotation(Unknown Source:0)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayRotation$2.lambda$continueRotateDisplay$0(DisplayRotation.java:233)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.server.wm.DisplayRotation$2$$ExternalSyntheticLambda0.accept(Unknown Source:10)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.internal.util.function.pooled.PooledLambdaImpl.doInvoke(PooledLambdaImpl.java:295)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.internal.util.function.pooled.PooledLambdaImpl.invoke(PooledLambdaImpl.java:204)
04-21 16:11:07.823 6355 6376 E biubiubiu: at com.android.internal.util.function.pooled.OmniFunction.run(OmniFunction.java:97)
2.1.3.2 onResize
这里的代码调用链也是 DisplayContent::sendNewConfiguration 触发的,当前主要分析的是横竖屏切换后,屏幕宽高肯定是是改变了宽高的(交换),那窗口大小是要改变的,也就是说会执行WindowState::onResize
所有的调用链以及部分解释在下面这张图里。
看一下 WindowState::onResize
ini
# WindowState
@Override
void onResize() {
final ArrayList<WindowState> resizingWindows = mWmService.mResizingWindows;
if (mHasSurface && !isGoneForLayout() && !resizingWindows.contains(this)) {
ProtoLog.d(WM_DEBUG_RESIZE, "onResize: Resizing %s", this);
resizingWindows.add(this);
}
if (isGoneForLayout()) {
mResizedWhileGone = true;
}
super.onResize();
}
这里有log打印
-
- 需要符合条件,才会进入if逻辑,最重要的就是 mHasSurface
-
- 把需要 resize 的窗口添加到 resizingWindows 集合,后续统一执行 resize 操作
在layout的时候会触发 RootWindowContainer::handleResizingWindows 对需要的窗口进行 resize 操作
csharp
# RootWindowContainer
private void handleResizingWindows() {
// 遍历集合中的窗口
for (int i = mWmService.mResizingWindows.size() - 1; i >= 0; i--) {
WindowState win = mWmService.mResizingWindows.get(i);
if (win.mAppFreezing || win.getDisplayContent().mWaitingForConfig) {
// Don't remove this window until rotation has completed and is not waiting for the
// complete configuration.
continue;
}
// 窗口执行resize
win.reportResized();
mWmService.mResizingWindows.remove(i);
}
}
配置相关的更新到这就结束了。
2.2 配置更新后确保显示正常
这部分的逻辑就涉及到 Activity 相关了,也是黑屏容易出现问题的部分。
java
# ActivityTaskManagerService
// Applies latest configuration and/or visibility updates if needed
// 如果需要,应用最新的配置和/或可见性更新
boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
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.
// 拿到顶部的 ActivityRecord
starting = mainRootTask.topRunningActivity();
}
if (starting != null) {
// 处理正确的 Configuration
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.
// 处理正确的可见效
mRootWindowContainer.ensureActivitiesVisible(starting, changes,
!PRESERVE_WINDOWS);
}
}
return kept;
}
这里又分2步:
-
- ensureActivityConfiguration 来确保Activity 有正确的 Configuration ,还有判断是否要重启 Activity
-
- 执行一次 ensureActivitiesVisible 来处理显示逻辑
2.2.1 Activity 处理(重启还是回调)
我当前的 demo 没有在清单文件处理,所以走的是重启逻辑。
arduino
# ActivityRecord
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
boolean ignoreVisibility) {
......//忽略其他代码,有很多的ProtoLog
// 计算出改变
final int changes = getConfigurationChanges(mTmpConfig);
// 这里会输出这次配置的改变,由此可判断当前Activity清单文件是否配置了忽略,要不要走重启流程
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration changes for %s, "
+ "allChanges=%s", this, Configuration.configurationDiffToString(changes));
......
// 重点 根据清单文件的配置判断是不是走重启逻辑
if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
configChangeFlags |= changes;
// 冻屏
startFreezingScreenLocked(globalChanges);
......
if (mState == PAUSING) {
......
} 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);
}
return false;
}
......
}
比如我当前是 relaunch 的流程,这个方法会打印下面这些日志,这也是判断是否走了重启流程重要关注的部分。
scss
04-21 16:11:07.832 6355 6376 V WindowManager: Ensuring correct configuration: ActivityRecord{b58886a u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t35}
04-21 16:11:07.832 6355 6376 V WindowManager: Configuration changes for ActivityRecord{b58886a u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t35}, allChanges={CONFIG_ORIENTATION, CONFIG_SCREEN_SIZE}
04-21 16:11:07.832 6355 6376 V WindowManager: Checking to restart com.google.android.dialer.extensions.GoogleDialtactsActivity: changed=0x480, handles=0x3, mLastReportedConfiguration={mGlobalConfig={1.0 ?mcc?mnc [zh_CN_#Hans,en_US] ldltr sw411dp w826dp h371dp 280dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1600, 720) mAppBounds=Rect(70, 0 - 1516, 720) mMaxBounds=Rect(0, 0 - 1600, 720) mDisplayRotation=ROTATION_90 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_90} s.120 fontWeightAdjustment=0} mOverrideConfig={1.0 ?mcc?mnc [zh_CN_#Hans,en_US] ldltr sw411dp w826dp h371dp 280dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1600, 720) mAppBounds=Rect(70, 0 - 1516, 720) mMaxBounds=Rect(0, 0 - 1600, 720) mDisplayRotation=ROTATION_90 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_90} s.2 fontWeightAdjustment=0}}
04-21 16:11:07.832 6355 6376 I WindowManager: Set freezing of Token{cae837 ActivityRecord{b58886a u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t35}}: visible=true freezing=false visibleRequested=true. java.lang.RuntimeException
04-21 16:11:07.832 7881 7881 D TransactionExecutor: tId:1663825981 End resolving transaction
04-21 16:11:07.832 6355 6376 V WindowManager: Config is relaunching ActivityRecord{b58886a u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t35}
2.2.1.1 Activity 重启
java
# ActivityRecord
private boolean shouldRelaunchLocked(int changes, Configuration changesConfig) {
int configChanged = info.getRealConfigChanged();
boolean onlyVrUiModeChanged = onlyVrUiModeChanged(changes, changesConfig);
// Override for apps targeting pre-O sdks
// If a device is in VR mode, and we're transitioning into VR ui mode, add ignore ui mode
// to the config change.
// For O and later, apps will be required to add configChanges="uimode" to their manifest.
if (info.applicationInfo.targetSdkVersion < O
&& requestedVrComponent != null
&& onlyVrUiModeChanged) {
configChanged |= CONFIG_UI_MODE;
}
return (changes&(~configChanged)) != 0;
}
这个方法其实就是根据清单文件配置 Activity 忽略哪些设置的改变而不重启,当前案例就是默认的配置,所以会走重启流程。
然后根据日志走的是 ActivityRecord::relaunchActivityLocked
scss
# ActivityRecord
void relaunchActivityLocked(boolean preserveWindow) {
......
// 正常为true
final boolean andResume = shouldBeResumed(null /*activeActivity*/);
......
// 这里的events 日志很重要,可以知道到底走的哪个逻辑
if (andResume) {
// wm_relaunch_resume_activity
EventLogTags.writeWmRelaunchResumeActivity(mUserId, System.identityHashCode(this),
task.mTaskId, shortComponentName);
} else {
// wm_resume_activity
EventLogTags.writeWmRelaunchActivity(mUserId, System.identityHashCode(this),
task.mTaskId, shortComponentName);
}
// 重点 开始冻屏
startFreezingScreenLocked(0);
try {
// 状态改变
ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
(andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
forceNewConfig = false;
// 设置 allDrawn 和 mLastAllDrawn 为false
startRelaunching();
// 构建执行 ActivityRelaunchItem 事务
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults,
pendingNewIntents, configChangeFlags,
new MergedConfiguration(getProcessGlobalConfiguration(),
getMergedOverrideConfiguration()),
preserveWindow);
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(isTransitionForward());
} else {
lifecycleItem = PauseActivityItem.obtain();
}
final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
transaction.addCallback(callbackItem);
transaction.setLifecycleStateRequest(lifecycleItem);
// 执行
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
// Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
// request resume if this activity is currently resumed, which implies we aren't
// sleeping.
} catch (RemoteException e) {
ProtoLog.i(WM_DEBUG_STATES, "Relaunch failed %s", e);
}
if (andResume) {
// 日志
ProtoLog.d(WM_DEBUG_STATES, "Resumed after relaunch %s", this);
results = null;
newIntents = null;
mAtmService.getAppWarningsLocked().onResumeActivity(this);
} else {
removePauseTimeout();
setState(PAUSED, "relaunchActivityLocked");
}
// 从stop集合移除
mTaskSupervisor.mStoppingActivities.remove(this);
......
}
2.2.2 执行一次 ensureActivitiesVisible
这个里是常见的 ensureActivitiesVisible 流程,目的就是确保当前屏幕上正确的 Activity 显示,这流程说通用且常见的。 在【Activity启动流程-2】 1.1.2 ensureActivitiesVisible分支 介绍过了
3. 总结
这部分处理的事情还是挺多的
-
- 计算新的 DisplayInfo ,Configuration
-
- 把新计算的 Configuration 分发到各个进程和容器,让他们根据新的 Configuration 更新自己的 Configuration
-
- 移动截图的图层,并处理窗口的 resize 逻辑
-
- 冻屏
-
- 常见的判断 Activity 旋转后是否需要走重启流程
-
- 在执行一次 ensureActivitiesVisible 流程确保显示正确
下一篇开始看旋转动画的相关逻辑