本文基于Android 11.0源码分析
前言
在上一篇文章中,主要对获取屏幕方向值的流程进行了总结,这篇文章中,将接着上篇的流程,对以下几点功能实现进行分析:
-
根据屏幕方向值+当前窗口属性,确定显示方向;
-
逻辑屏方向的更新;
-
转屏动画的执行。
1.WMS#updateRotation()
接着上文分析,当DisplayRotation发现屏幕方向改变后,通过WindowManagerService#updateRotation()方法通知WMS进行全局更新。WMS#updateRotation()方法中直接调用updateRotationUnchecked()方法:
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
/**
* @param alwaysSendConfiguration 是否要进行Configuration的更新
* @param forceRelayout 是否强制进行layout操作
*/
private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
......
try {
synchronized (mGlobalLock) {
boolean layoutNeeded = false;
final int displayCount = mRoot.mChildren.size();
// 遍历DisplayContent
for (int i = 0; i < displayCount; ++i) {
final DisplayContent displayContent = mRoot.mChildren.get(i);
// 进入DisplayContent更新屏幕方向值
final boolean rotationChanged = displayContent.updateRotationUnchecked();
if (rotationChanged) {
mAtmService.getTaskChangeNotificationController()
.notifyOnActivityRotation(displayContent.mDisplayId);
}
// 是否要进行layout
if (!rotationChanged || forceRelayout) {
displayContent.setLayoutNeeded();
layoutNeeded = true;
}
// 是否要进行Configuration的更新
if (rotationChanged || alwaysSendConfiguration) {
displayContent.sendNewConfiguration();
}
}
// 进行layout
if (layoutNeeded) {
mWindowPlacerLocked.performSurfacePlacement();
}
}
}
.......
}
在以上方法中,会对每一个DisplayContent进行方向更新,并会在DisplayContent中则直接调用DisplayRotation.updateRotationUnchecked()方法,然后根据返回结果和参数决定是否进行Configuration的更新和layout操作。
DisplayRotation.updateRotationUnchecked()方法如下:
//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
boolean updateRotationUnchecked(boolean forceUpdate) {
final int displayId = mDisplayContent.getDisplayId();
//......
// 获取该DisplayContent的转屏动画对象
final ScreenRotationAnimation screenRotationAnimation =
mDisplayContent.getRotationAnimation();
// 如果当前有正在执行的转屏动画,则不会进行后续动作
if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
return false;
}
// 如果当前屏幕处于冻结状态,则不会更新
if (mService.mDisplayFrozen) {
return false;
}
// 如果顶层Activity执行最近任务动画,且该Activity设置了一个固定方向,则不会更新
if (mDisplayContent.mFixedRotationTransitionListener
.isTopFixedOrientationRecentsAnimating()) {
return false;
}
}
// 如果WMS没有启动完成,则不会更新
if (!mService.mDisplayEnabled) {
return false;
}
// 当前(未更新前)屏幕方向
final int oldRotation = mRotation;
// 最近一次应用设置的显示方向
final int lastOrientation = mLastOrientation;
// 1.根据给定的显示方向确定一个屏幕方向
final int rotation = rotationForOrientation(lastOrientation, oldRotation);
// 屏幕方向没有发生变化,则不会更新,否则说明屏幕方向值发生了变化
if (oldRotation == rotation) {
return false;
}
// 如果屏幕方向值转了不超过两次,则设置mDisplayContent.mWaitingForConfig为true,以防还有一次转动,一个小优化
if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {
mDisplayContent.mWaitingForConfig = true;
}
// 更新当前屏幕方向
mRotation = rotation;
//
mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
// 表示需要进行layout操作
mDisplayContent.setLayoutNeeded();
// 选择转屏动画
if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
// 无缝衔接动画
prepareSeamlessRotation();
} else {
prepareNormalRotationAnimation();
}
// 给SystemUI发送一个屏幕方向变化的消息,会在SystemUI中的DisplayChangeController中处理,并会回调fw
startRemoteRotation(oldRotation, mRotation);
return true;
}
以上方法中,首先会进行一些判断来确定是否需要更新屏幕方向值,只有在满足更新条件时才会进行更新,这些条件如下:
-
如果当前有正在执行的转屏动画,则不会更新;
-
如果当前屏幕依然处于冻结状态,则不会更新;
-
如果顶层Activity执行最近任务动画,且该Activity设置了一个固定方向,则不会更新;
-
如果WMS没有启动完成,则不会更新;
如果通过以上四个条件的验证,那么接下来就会根据当前屏幕方向值和当前显示方向(未更新前的值),结合Sensor检测到的屏幕方向值、Activity设置的方向状态等获得一个最终的显示方向。
1.1 根据屏幕方向值+当前窗口属性,确定显示方向
这部分流程在rotationForOrientation()方法中进行,代码如下:
//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
int rotationForOrientation(@ScreenOrientation int orientation,
@Surface.Rotation int lastRotation) {
// 如果用户固定了屏幕方向,则返回用户设置的屏幕方向值
if (isFixedToUserRotation()) {
return mUserRotation;
}
// 获取Sensor检测到的屏幕方向值
int sensorRotation = mOrientationListener != null
? mOrientationListener.getProposedRotation() // may be -1
: -1;
if (sensorRotation < 0) {
sensorRotation = lastRotation;
}
// lid switch状态
final int lidState = mDisplayPolicy.getLidState();
// 各种dock连接状态
final int dockMode = mDisplayPolicy.getDockMode();
final boolean hdmiPlugged = mDisplayPolicy.isHdmiPlugged();
final boolean carDockEnablesAccelerometer =
mDisplayPolicy.isCarDockEnablesAccelerometer();
final boolean deskDockEnablesAccelerometer =
mDisplayPolicy.isDeskDockEnablesAccelerometer();
// 表示要选择的屏幕方向优先值
final int preferredRotation;
if (!isDefaultDisplay) {
// 第二屏幕永远使用用户设置的屏幕方向值
preferredRotation = mUserRotation;
} else if (lidState == LID_OPEN && mLidOpenRotation >= 0) {
// lid_switch open时,使用lid open指定的值
preferredRotation = mLidOpenRotation;
} else if (dockMode == Intent.EXTRA_DOCK_STATE_CAR
........
} else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
// Activity将显示方向锁定为其当前的任一显示方向,因此继续使用当前屏幕方向值
preferredRotation = lastRotation;
} else if (!mSupportAutoRotation) {
// 不支持自动旋转功能
preferredRotation = -1;
} else if ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
// 如果开启了自动旋转,且显示方向值满足以下几种,
&& (orientation == ActivityInfo.SCREEN_ORIENTATION_USER
|| orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|| orientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|| orientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER))
|| orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|| orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|| orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
// mAllowAllRotations表示是否允许全方位都进行旋转,有的设备不能旋转180度,原因就是这里为false
if (mAllowAllRotations == ALLOW_ALL_ROTATIONS_UNDEFINED) {
mAllowAllRotations = mContext.getResources().getBoolean(
R.bool.config_allowAllRotations)
? ALLOW_ALL_ROTATIONS_ENABLED
: ALLOW_ALL_ROTATIONS_DISABLED;
}
// sensor检测当前屏幕方向值为180度
if (sensorRotation != Surface.ROTATION_180
// 允许全方位旋转
|| mAllowAllRotations == ALLOW_ALL_ROTATIONS_ENABLED
// 显示方向为fullsensor
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
// 显示方向为fullsensor
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
// 满足任一条件,都会优先使用sensor检测到的屏幕方向值
preferredRotation = sensorRotation;
} else {
// 否则使用当前屏幕方向值
preferredRotation = lastRotation;
}
} else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED
// 如果关闭了方向旋转且最近一次显示方向满足以下条件时,使用用户设置的屏幕方向值
&& orientation != ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
&& orientation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
&& orientation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
&& orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
&& orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
// 优先使用用户设置屏幕方向值
preferredRotation = mUserRotation;
} else {
// 说明没有优先使用的条件
preferredRotation = -1;
}
// 根据最近一次显示方向确定
switch (orientation) {
// 显示方向为竖屏
case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
if (isAnyPortrait(preferredRotation)) {
return preferredRotation;
}
return mPortraitRotation;
case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
if (isLandscapeOrSeascape(preferredRotation)) {
return preferredRotation;
}
return mLandscapeRotation;
case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
if (isAnyPortrait(preferredRotation)) {
return preferredRotation;
}
return mUpsideDownRotation;
case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
if (isLandscapeOrSeascape(preferredRotation)) {
return preferredRotation;
}
return mSeascapeRotation;
case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
if (isLandscapeOrSeascape(preferredRotation)) {
return preferredRotation;
}
if (isLandscapeOrSeascape(lastRotation)) {
return lastRotation;
}
return mLandscapeRotation;
case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
if (isAnyPortrait(preferredRotation)) {
return preferredRotation;
}
if (isAnyPortrait(lastRotation)) {
return lastRotation;
}
return mPortraitRotation;
default:
// 说明显示方向为USER, UNSPECIFIED, NOSENSOR, SENSOR,FULL_SENSOR时,返回preferredRotation值或0度
if (preferredRotation >= 0) {
return preferredRotation;
}
return Surface.ROTATION_0;
}
}
以上方法中,首先根据各种系统状态确定一个优先使用的屏幕方向,然后再根据最近一次Activity设置的的显示方向,确认最终的屏幕方向,这些流程的决策流程总结如下:
首先确定优先屏幕方向值:
-
如果用户固定了屏幕方向值,则直接返回用户设置的屏幕方向值。固定屏幕方向使用WMS#setFixedToUserRotation()方法;
-
如果不是默认屏,则直接返回用户设置的屏幕方向值。通过WMS#freezeDisplayRotation(int displayId, int rotation)方法设置指定屏幕方向值;
-
如果lid状态处于OPEN状态,且对该模式下配置了指定的屏幕方向,则优先使用指定的lid_switch open时的屏幕方向值。lid_switch通过inputmanger上报,如霍尔传感器的事件就是lid_switch的形式上报;
-
如果设备处于各种Dock模式,且对该模式下配置了指定的屏幕方向,则优先使用该指定的屏幕方向,这种场景不多,这里我们直接跳过它的流程;
-
如果当前Activity设置的显示方向值为LOCKED,即将屏幕方向锁定为其当前的任一显示方向,则优先使用当前屏幕方向值。Activity显示方向可通过android:screenOrientation="locked"或Activity#setRequestedOrientation(int requestedOrientation)方法设置,下同;
-
如果不支持自动旋转功能,则表示优先屏幕方向的变量将会被置为-1。通过config.xml中”config_supportAutoRotation“配置是否支持自动旋转功能;
-
如果开启了自动旋转,且Activity设置的显示方向值为USER、UNSPECIFIED、USER_LANDSCAPE、USER_PORTRAIT、FULL_USER,则优先使用Sensor获得的屏幕方向或最近一次的屏幕方向值,这几种方向值都是支持根据Sensor来调整方向的;
-
如果Activity设置的显示方向值为SENSOR、FULL_SENSOR、SENSOR_LANDSCAPE、SENSOR_PORTRAIT,这种情况下同上条;
-
如果关闭了自动旋转,且Activity设置的显示方向值为NOSENSOR、LANDSCAPE、PORTRAIT、REVERSE_LANDSCAPE、REVERSE_PORTRAIT,这种情况下,则优先使用用户设置的屏幕方向;
-
如果以上条件都不满足,则优先屏幕方向变量将会被置为-1。
其次确定最终屏幕方向值:
-
如果最近一次Activity设置的显示方向值为PORTRAIT,则屏幕方向一般返回Surface.ROTATION_0;
-
如果最近一次Activity设置的显示方向值为LANDSCAPE,则屏幕方向一般返回Surface.ROTATION_90;
-
如果最近一次Activity设置的显示方向值为REVERSE_PORTRAIT,则屏幕方向一般返回Surface.ROTATION_180;
-
如果最近一次Activity设置的显示方向值为REVERSE_LANDSCAPE,则屏幕方向一般返回Surface.ROTATION_270;
-
如果最近一次Activity设置的显示方向值为SENSOR_LANDSCAPE或USER_LANDSCAPE,则屏幕方向一般返回Surface.ROTATION_90或Surface.ROTATION_270;
-
如果最近一次Activity设置的显示方向值为SENSOR_PORTRAIT或USER_PORTRAIT,则屏幕方向一般返回ROTATION_0或Surface.ROTATION_180;
-
如果以上条件都不满足,则当优先屏幕方向大于等于0时,屏幕方向返回优先值,否则返回Surface.ROTATION_0。
上面这些步骤看起来令人非常难受,但其实这段逻辑就是android:screenOrientation属性功能的实现之处,了解该属性的各个值之后就自然明白了。经过rotationForOrientation()方法后,将得出一个屏幕方向值。
回到DisplayRotation#updateRotationUnchecked()方法中,如果得到的屏幕方向值发生了变化,说明就需要更新显示方向了,接下来将选择转屏动画,用来执行旋转动画。
1.2 确定转屏方式和转屏动画类型
转屏根据是否有动画分为两种:
-
一种是Seamless Rotation(无缝衔接),使用它时不会对屏幕进行冻结,直接由一个方向过渡到另一个方向;
-
一种是Normal Rotation,使用它时,将会对旧方向截图并冻结屏幕,并在更新完成后,通过动画缓慢过渡到新的显示方向。
根据动画执行方式,又可以分为四类,并且这四类是应用可以自行设置的,通过WindowManager.LayoutParams.rotationAnimation属性来指定:
-
ROTATION_ANIMATION_ROTATE:默认执行方式;
-
ROTATION_ANIMATION_CROSSFADE:fade-in、fade-out的方式;
-
ROTATION_ANIMATION_JUMPCUT:当前窗口显示或退出时立即跳入/跳出;
-
ROTATION_ANIMATION_SEAMLESS:使用Seamless Rotation,但是如果不支持Seamless Rotation时,将使用CROSSFADE方式;
以上四个值只能用在全屏非透明窗口上。
在触发转屏流程后,首先是选择转屏动画的类型,通过shouldRotateSeamlessly()方法来是否使用Seamless Rotation:
//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
/**
* @param oldRotation 旧屏幕方向
* @param newRotation 新屏幕方向
* @param forceUpdate 是否强制进行更新
* @return true表示采用seamless Rotation
*/
boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
if (mDisplayContent.hasTopFixedRotationLaunchingApp()) {
return true;
}
// 获取当前全屏非透明窗口
final WindowState w = mDisplayPolicy.getTopFullscreenOpaqueWindow();
// 如果当前全屏非透明窗口没有获得焦点,不使用Seamless Rotation
if (w == null || w != mDisplayContent.mCurrentFocus) {
return false;
}
// 如果顶层窗口动画属性非ROTATION_ANIMATION_SEAMLESS,或正在执行动画,不使用Seamless Rotation
if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.isAnimatingLw()) {
return false;
}
// 如果屏幕方向值相对发生了180度,不使用Seamless Rotation
if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) {
return false;
}
// 其他几类场景不常见,略去
......
// 如果当前全屏非透明窗口的mSeamlesslyRotated属性被设置为false,不使用Seamless Rotation
if (!forceUpdate && mDisplayContent.getWindow(win -> win.mSeamlesslyRotated) != null) {
return false;
}
// 不满足以上条件,返回true
return true;
}
正常情况下,特殊场景除外,大部分场景都使用Normal Rotation,即在旋转屏幕的时候,会以动画的形式进行。
使用Normal Rotation时,首先通过prepareNormalRotationAnimation()方法选择动画执行方式,然后将开始冻结屏幕:
//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
void prepareNormalRotationAnimation() {
// 取消正在执行的Seamless Rotation
cancelSeamlessRotation();
// 选择动画执行方式
final RotationAnimationPair anim = selectRotationAnimation();
// 冻结屏幕
mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
}
在selectRotationAnimation()方法中选择动画执行方式时,会根据当前顶层窗口的WindowManager.LayoutParams#rotationAnimation来选择一组动画,选择动画执行方式的流程比较简单,这里就直接略去。
app如果想操作横竖屏动画,通过WindowManager.LayoutParams#rotationAnimation控制,就是在selectRotationAnimation()这里选择的。
1.3 冻结屏幕
如果不使用Seamless Rotation动画,那么在确定好转屏动画方式后,将会冻结屏幕。冻结屏幕就是把显示内容冻结在当前的内容上,冻结的实现原理很简单:
-
冻结掉Input事件;
-
对当前屏幕截图,并显示在最顶层。
之所以在转屏时需要冻结屏幕,是因为屏幕的冻结和解冻,都可以通过参数以动画的形式进行,当屏幕实际旋转完成后,再解冻后以动画形式回到新显示方向,用户体验更好。而转屏动画的实现,也和屏幕冻结绑定在一起。
此时,新的屏幕方向、转屏动画类型和方式都得到了确定,并且进行了屏幕的冻结。
1.4 通知SystemUI方向发生变化
回到DisplayRotation#updateRotationUnchecked()中,接下来执行startRemoteRotation()方法,这个方法将方向旋转信息通知给SystemUI,让它对应的做相关处理:
//frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
private void startRemoteRotation(int fromRotation, int toRotation) {
// 表示正在等待客户端的回调
mIsWaitingForRemoteRotation = true;
try {
// 通知SystemUI模块方向旋转信息
mService.mDisplayRotationController.onRotateDisplay(mDisplayContent.getDisplayId(),
fromRotation, toRotation, mRemoteRotationCallback);
} catch (RemoteException e) {
}
}
当SystemUI收到调用并处理完毕后,会回调DisplayRotation.mRemoteRotationCallback的continueRotateDisplay()方法来通知WMS模块。
此时,DisplayRotation#updateRotationUnchecked()方法执行完毕,并返回true。接下来将等待SystemUI的回调......。
1.5 SystemUI收到调用后,向system_server进行回调
在收到SystemUI回调或者回调超时时,DisplayRotation#continueRotation()方法将会执行,来继续进行旋转流程:
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private void continueRotation(int targetRotation, WindowContainerTransaction t) {
synchronized (mService.mGlobalLock) {
mIsWaitingForRemoteRotation = false;
// defer layout
mService.mAtmService.deferWindowLayout();
try {
// Configuration的更新
mDisplayContent.sendNewConfiguration();
} finally {
// continue layout
mService.mAtmService.continueWindowLayout();
}
}
}
以上方法中,通过DisplayContent#sendNewConfiguration()方法进行新的Configuration的更新。
1.6 逻辑屏配置更新
Configuration对象的更新入口方法如下:
//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
void sendNewConfiguration() {
......
final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
// configUpdated为true时,后面流程不执行
if (configUpdated) {
return;
}
......
}
从sendNewConfiguration()方法开始,将会进行全局配置的更新,对这部分流程这里不做详细介绍,只做一个简单的介绍。
在sendNewConfiguration()方法中,会调用updateDisplayOverrideConfigurationLocked()方法更新Display的覆盖Configuration对象:
-
首先会创建一个新的Configuration对象;
-
然后在computeScreenConfiguration()方法中对这个Configuration对象进行填充屏幕相关的属性,比如范围边界、屏幕密度、布局、键盘、逻辑屏信息等.....。
而在更新逻辑屏信息时,就会根据DisplayRotation#mRotation对屏幕宽高进行设置:
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) {
// 从DisplayRotation中获取屏幕方向值
final int rotation = getRotation();
// 屏幕方向值为1(90度)和3(270度)时,需要交换屏幕宽高
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
// 根据高度和宽度获取logical display宽高
final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
......
// 设置屏幕方向值
mDisplayInfo.rotation = rotation;
......
// 向DMS中设置WMS中的逻辑屏信息
mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
overrideDisplayInfo);
......
return mDisplayInfo;
}
此时,如果方向发生了旋转,逻辑屏幕的宽和高将进行交换。
- 对新创建Configuration对象填充完毕后,接下来,通过updateDisplayOverrideConfigurationLocked()方法进一步执行更新override 配置的流程。在该方法中,如果是Default Display,则不仅需要更新override Configuration,还需要更新全局Configuration;如果是非Default Display,则仅更新override Configuration:
//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
ActivityRecord starting, boolean deferResume,
ActivityTaskManagerService.UpdateConfigurationResult result) {
......
mAtmService.deferWindowLayout();
try {
if (values != null) {
if (mDisplayId == DEFAULT_DISPLAY) {
// 更新全局Configuration
changes = mAtmService.updateGlobalConfigurationLocked(values,
false /* initLocale */, false /* persistent */,
UserHandle.USER_NULL /* userId */, deferResume);
} else {
// 更新Override Configuration
changes = performDisplayOverrideConfigUpdate(values, deferResume);
}
}
}
return kept;
}
全局Configuration的更新通过ATMS#updateGlobalConfigurationLocked()方法进行,Override Configuration的更新通过DisplayContent#performDisplayOverrideConfigUpdate()方法进行。
在更新Override Configuration过程中,会在onRequestedOverrideConfigurationChanged()方法中进行全局变量mRequestedOverrideConfiguration的更新,并且在这里会判断是否屏幕方向发生变化,在发生变化后,进行方向的更新:
//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
// 获取当前override Configuration
final Configuration currOverrideConfig = getRequestedOverrideConfiguration();
final int currRotation = currOverrideConfig.windowConfiguration.getRotation();
final int overrideRotation = overrideConfiguration.windowConfiguration.getRotation();
// 屏幕方向发生变化
if (currRotation != ROTATION_UNDEFINED && currRotation != overrideRotation) {
applyRotationAndFinishFixedRotation(currRotation, overrideRotation);
}
mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration);
// 更新mRequestedOverrideConfiguration
super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
mCurrentOverrideConfigurationChanges = 0;
mWmService.setNewDisplayOverrideConfiguration(overrideConfiguration, this);
mAtmService.addWindowLayoutReasons(
ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
}
如果当前override Configuration对象和新的override Configuration对象中mRotation属性不一致,说明屏幕方向发生了变化,接下来将会将会通过DC#applyRotationAndFinishFixedRotation()方法更新屏幕显示方向,这个方法中又通过applyRotation()方法进行。
1.7 使用新屏幕方向
在applyRotation()方法中,会使用新屏幕方向,进行相关属性的更新,这个过程中会向DMS中发起请求,进行逻辑屏的配置流程:
//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
private void applyRotation(final int oldRotation, final int rotation) {
// 更新WindowOrientationListener#mCurrentRotation变量
mDisplayRotation.applyCurrentRotation(rotation);
// Seamless rotation 没有动画
final boolean rotateSeamlessly = mDisplayRotation.isRotatingSeamlessly();
final Transaction transaction = getPendingTransaction();
// 转屏动画对象
ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
? null : getRotationAnimation();
// 再更新逻辑屏信息,确保屏幕方向的正确性
updateDisplayAndOrientation(getConfiguration().uiMode, null /* outConfig */);
// 转屏动画设置初始位置矩阵
if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
screenRotationAnimation.setRotation(transaction, rotation);
}
// 更新WindowState中Seamless Rotation相关属性
forAllWindows(w -> {
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
}, true /* traverseTopToBottom */);
// 调用DMS更新Display配置
mWmService.mDisplayManagerInternal.performTraversal(transaction);
// 执行动画
scheduleAnimation();
// 更新WindowState#mOrientationChanging属性
forAllWindows(w -> {
if (w.mHasSurface && !rotateSeamlessly) {
w.setOrientationChanging(true);
// mOrientationChangeComplete表示显示方向的更新是否完成
mWmService.mRoot.mOrientationChangeComplete = false;
w.mLastFreezeDuration = 0;
}
// 表示需要通知客户端发生了转屏
w.mReportOrientationChanged = true;
}, true /* traverseTopToBottom */);
// 发起RotationWatcher#onRotationChanged()
for (int i = mWmService.mRotationWatchers.size() - 1; i >= 0; i--) {
final WindowManagerService.RotationWatcher rotationWatcher
= mWmService.mRotationWatchers.get(i);
if (rotationWatcher.mDisplayId == mDisplayId) {
try {
rotationWatcher.mWatcher.onRotationChanged(rotation);
} catch (RemoteException e) {
// Ignore
}
}
}
}
以上方法中,主要流程有:
-
首先通过DisplayRotation#applyCurrentRotation()方法,更新WindowOrientationListener#mCurrentRotation变量;
-
如果转屏方式为Seamless Rotation,则不需要任何动画;否则,说明需要转屏动画,会设置初始转屏动画的转移矩阵,然后遍历WindowState, 将WindowState#mOrientationChanging属性设置为true,表示处于转屏过程中。
-
执行updateDisplayAndOrientation()更新逻辑屏属性;
-
通过mDisplayManagerInternal.performTraversal()配置更新Display属性;
-
接下来,将mWmService.mRoot.mOrientationChangeComplete值设置为false,这个值表示显示方向的更新是否完成,当转屏动画完成后,该值将会被设置为true,开始解冻流程。
-
最后发起RotationWatcher#onRotationChanged()调用,通知所有的RotationWatcher方向发生了变化。
此时,applyRotation()方法中的流程执行完毕。
接下来将等待VSync信号的到来,进行刷新定位流程,在这个流程中,将解冻屏幕,并执行转屏动画。
1.8 解冻屏幕,执行转屏动画
之后,当WindowAnimator对象收到VSync信号后,开始进行动画,在这个动画中:
//frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java
private void animate(long frameTimeNs) {
......
// SET_ORIENTATION_CHANGE_COMPLETE标记位表示方向改变完成
mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
// 开启事务
mService.openSurfaceTransaction();
......
final boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
// 更新mOrientationChangeComplete属性
final boolean doRequest = mBulkUpdateParams != 0 && mService.mRoot.copyAnimToLayoutParams();
// 发起WMSS遍历
if (hasPendingLayoutChanges || doRequest) {
mService.mWindowPlacerLocked.requestTraversal();
}
......
SurfaceControl.mergeToGlobalTransaction(mTransaction);
// 关闭事务,进行提交
mService.closeSurfaceTransaction("WindowAnimator");
}
在copyAnimToLayoutParams()方法中,会将mOrientationChangeComplete属性更新为true,表示显示方向更新完毕:
//frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
boolean copyAnimToLayoutParams() {
boolean doRequest = false;
final int bulkUpdateParams = mWmService.mAnimator.mBulkUpdateParams;
.......
if ((bulkUpdateParams & SET_ORIENTATION_CHANGE_COMPLETE) == 0) {
mOrientationChangeComplete = false;
} else {
mOrientationChangeComplete = true;
mLastWindowFreezeSource = mWmService.mAnimator.mLastWindowFreezeSource;
if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
doRequest = true;
}
}
.....
return doRequest;
}
这个值变为true后,在之后的遍历操作中,将进行解冻处理。
屏幕的解冻是由WMS#stopFreezingDisplayLocked()方法进行,当WMS模块组件收到VSync信号后执行定位过程时,会调用此方法进行解冻流程:
void performSurfacePlacementNoTrace() {
......
// 显示方向是否改变完成
if (mOrientationChangeComplete) {
if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
}
// 解冻
mWmService.stopFreezingDisplayLocked();
}
......
}
以上逻辑中可以看到,是否解冻取决于mOrientationChangeComplete属性,该值在上一步流程中已经被设置为true,下面来看具体的解冻流程:
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
void stopFreezingDisplayLocked() {
......
// 重置mDisplayFrozen为false,表示不处于冻结状态
mDisplayFrozen = false;
// 解冻IMS
mInputManagerCallback.thawInputDispatchingLw();
boolean updateRotation = false;
// 获取ScreenRotationAnimation对象
ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
: displayContent.getRotationAnimation();
if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
DisplayInfo displayInfo = displayContent.getDisplayInfo();
// Get rotation animation again, with new top window
if (!displayContent.getDisplayRotation().validateRotationAnimation(
mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
mExitAnimId = mEnterAnimId = 0;
}
// 执行转屏动画
if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
mTransaction.apply();
// 直接过渡
} else {
screenRotationAnimation.kill();
displayContent.setRotationAnimation(null);
updateRotation = true;
}
} else {
if (screenRotationAnimation != null) {
screenRotationAnimation.kill();
displayContent.setRotationAnimation(null);
}
updateRotation = true;
}
......
}
以上方法中,将解冻屏幕,并执行转屏动画。当转屏动画执行完毕后,整个显示方向的旋转过程也就完成了。
网友评论