Android 11---WMS之横竖屏切换流程详解
发布日期:2021-06-29 13:07:43 浏览次数:3 分类:技术文章

本文共 44984 字,大约阅读时间需要 149 分钟。

本文以Activity.setRequestedOrientation为入口梳理下横竖屏切换的详细流程。 代码均是基于最新的11.0版本。

第一篇主要讲了横竖屏切换时的准备操作: 更新方向,执行冻屏,截图显示以及计算更新基于新的方向的DisplayInfo和Configuration。

第二篇主要讲下如何将更新后的Configuration通知到树形的窗口结构以及应用中。

第三篇主要讲下解冻的流程以及横竖屏旋转动画的流程。

场景:假设此时手机处于横屏90度状态,当前Activity处于前台resumed状态,点击button设置竖屏, 逻辑如下:

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

整个流程如下图所示:

下面来一步步进行梳理:

Step 1. Activity.setRequestedOrientation

/**     * Change the desired orientation of this activity.  If the activity     * is currently in the foreground or otherwise impacting the screen     * orientation, the screen will immediately be changed (possibly causing     * the activity to be restarted). Otherwise, this will be used the next     * time the activity is visible.  注释已经解释得很详细了,就是改变activity的期望方向     *     * @param requestedOrientation An orientation constant as used in     * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.     */    public void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
if (mParent == null) {
// 现在版本已经不使用ActivityParent,因此mParent一直为null try {
ActivityTaskManager.getService().setRequestedOrientation( mToken, requestedOrientation); } catch (RemoteException e) {
// Empty } } else {
mParent.setRequestedOrientation(requestedOrientation); } }

先看下设置的参数: AcitivityInfo.screenOrientation

/** * Information you can retrieve about a particular application * activity or receiver. This corresponds to information collected * from the AndroidManifest.xml's <activity> and * <receiver> tags. */public class ActivityInfo extends ComponentInfo implements Parcelable {
/** @hide */ @IntDef(prefix = {
"SCREEN_ORIENTATION_" }, value = {
SCREEN_ORIENTATION_UNSET, SCREEN_ORIENTATION_UNSPECIFIED, SCREEN_ORIENTATION_LANDSCAPE, SCREEN_ORIENTATION_PORTRAIT, SCREEN_ORIENTATION_USER, SCREEN_ORIENTATION_BEHIND, SCREEN_ORIENTATION_SENSOR, SCREEN_ORIENTATION_NOSENSOR, SCREEN_ORIENTATION_SENSOR_LANDSCAPE, SCREEN_ORIENTATION_SENSOR_PORTRAIT, SCREEN_ORIENTATION_REVERSE_LANDSCAPE, SCREEN_ORIENTATION_REVERSE_PORTRAIT, SCREEN_ORIENTATION_FULL_SENSOR, SCREEN_ORIENTATION_USER_LANDSCAPE, SCREEN_ORIENTATION_USER_PORTRAIT, SCREEN_ORIENTATION_FULL_USER, SCREEN_ORIENTATION_LOCKED }) @Retention(RetentionPolicy.SOURCE) public @interface ScreenOrientation {
}}

一个int型变量,可以设置的参数上面都已经列出来,详细的解释可以到ActivityInfo.java文件中查看。然后就通过binder调用到系统服务端。

Step 2. ActivityTaskManagerService.setRequestedOrientation

/** * System service for managing activities and their containers (task, stacks, displays,... ). * * {@hide} */public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}通过代码来看,系统服务端就是ActivityTaskManagerService @Override public void setRequestedOrientation(IBinder token, int requestedOrientation) {
synchronized (mGlobalLock) {
// 整个过程是在锁里执行的 ActivityRecord r = ActivityRecord.isInStackLocked(token); // 通过token找到对应的ActivityRecord实例 if (r == null) {
return; } final long origId = Binder.clearCallingIdentity(); try {
r.setRequestedOrientation(requestedOrientation); } finally {
Binder.restoreCallingIdentity(origId); } } }

Step 3. ActivityRecord.setRequestedOrientation

/** * An entry in the history stack, representing an activity.  应用端的Acitivity在系统am侧是以ActivityRecord形式存在的。 */final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
} void setRequestedOrientation(int requestedOrientation) {
// mayFreezeScreenLocked()方法就是判断Activity所在进程是否处于可freeze的状态 // return hasProcess() && !app.isCrashing() && !app.isNotResponding(); setOrientation(requestedOrientation, mayFreezeScreenLocked()); // 通知相应的listener mAtmService.getTaskChangeNotificationController(). notifyActivityRequestedOrientationChanged(task.mTaskId, requestedOrientation); } private void setOrientation(int requestedOrientation, boolean freezeScreenIfNeeded) {
final IBinder binder = (freezeScreenIfNeeded && appToken != null) ? appToken.asBinder() : null; // 通过代码来看, ActivityRecord,WindowToken都并未复写该方法, 因此直接看WindowContainer. setOrientation(requestedOrientation, binder, this); // Push the new configuration to the requested app in case where it's not pushed, e.g. when // the request is handled at task level with letterbox. if (!getMergedOverrideConfiguration().equals( mLastReportedConfiguration.getMergedConfiguration())) {
ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); } }

Step 4. WindowContainer.setOrientation

/**     * Sets the specified orientation of this container. It percolates this change upward along the     * hierarchy to let each level of the hierarchy a chance to respond to it.     *     * @param orientation the specified orientation. Needs to be one of {@link     *      android.content.pm.ActivityInfo.ScreenOrientation}.     * @param freezeDisplayToken uses this token to freeze display if orientation change is not     *                           done. Display will not be frozen if this is {@code null}, which     *                           should only happen in tests.     * @param requestingContainer the container which orientation request has changed. Mostly used     *                            to ensure it gets correct configuration.     */    void setOrientation(int orientation, @Nullable IBinder freezeDisplayToken,            @Nullable ConfigurationContainer requestingContainer) {
if (mOrientation == orientation) {
return; } mOrientation = orientation; // 更新ActivityRecord实例的方向值 final WindowContainer parent = getParent(); if (parent != null) {
if (getConfiguration().orientation != getRequestedConfigurationOrientation()) {
// Resolve the requested orientation. // 如果请求的方向与当前ActivityRecord方向不一致,以父节点的config更新当前Activity的config // 存在一种可能, Activity与当前的系统方向不一致,例如size compact mode,系统竖屏情况下,会强制 // 横屏unsizable activity缩放显示,但是此Activity仍是横屏方向,此时此Activity设置竖屏,就不会引起系统方向更新,只是更新Activity的config。 onConfigurationChanged(parent.getConfiguration()); } onDescendantOrientationChanged(freezeDisplayToken, requestingContainer); } }

就像view是以树形结构管理的,其实系统端的窗口也是以树形结构管理。画下10.0和11.0的窗口结构(11.0改动稍微大些)如下图所示:

Step 5. WindowContainer.onDescendantOrientationChanged

/**     * Called when this container or one of its descendants changed its requested orientation, and     * wants this container to handle it or pass it to its parent.     *     * @param freezeDisplayToken freeze this app window token if display needs to freeze     * @param requestingContainer the container which orientation request has changed     * @return {@code true} if handled; {@code false} otherwise.     */    boolean onDescendantOrientationChanged(@Nullable IBinder freezeDisplayToken,            @Nullable ConfigurationContainer requestingContainer) {
final WindowContainer parent = getParent(); if (parent == null) {
return false; } return parent.onDescendantOrientationChanged(freezeDisplayToken, requestingContainer); }

沿着树结构往上,该函数仅有Task和DisplayContent复写。针对全屏情况而言,这里仅看DisplayContent的相关逻辑。

Step 6. DisplayContent.onDescendantOrientationChanged

boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,            ConfigurationContainer requestingContainer) {
// 1. 综合当前信息判断是否更新方向,执行冻屏,更新config,displayInfo等信息 关键函数,代码量较大: Step 7 -- Step 22 final Configuration config = updateOrientation( getRequestedOverrideConfiguration(), freezeDisplayToken, false /* forceUpdate */); // If display rotation class tells us that it doesn't consider app requested orientation, // this display won't rotate just because of an app changes its requested orientation. Thus // it indicates that this display chooses not to handle this request. final boolean handled = getDisplayRotation().respectAppRequestedOrientation(); if (config == null) {
return handled; } if (handled && requestingContainer instanceof ActivityRecord) {
final ActivityRecord activityRecord = (ActivityRecord) requestingContainer; final boolean kept = updateDisplayOverrideConfigurationLocked(config, activityRecord, false /* deferResume */, null /* result */); activityRecord.frozenBeforeDestroy = true; if (!kept) {
mRootWindowContainer.resumeFocusedStacksTopActivities(); } } else {
// We have a new configuration to push so we need to update ATMS for now. // TODO: Clean up display configuration push between ATMS and WMS after unification. updateDisplayOverrideConfigurationLocked(config, null /* starting */, false /* deferResume */, null); } return handled; }

Step 7. DisplayContent.updateOrientation参数分析

final Configuration config = updateOrientation(
getRequestedOverrideConfiguration(), freezeDisplayToken, false /* forceUpdate */);
第2参数就是指向请求方向更新的activity,第3个参数判断是否强制更新,后面会讲到,这里主要看下第一个参数。

窗口管理架构结构都是继承WindowContainer,而WindowContainer继承了ConfigurationContainer。因此这里直接看到ConfigurationContainer. getRequestedOverrideConfiguration

/** Returns requested override configuration applied to this configuration container. */    public Configuration getRequestedOverrideConfiguration() {
return mRequestedOverrideConfiguration; } /** * Contains requested override configuration settings applied to this configuration container. */ private Configuration mRequestedOverrideConfiguration = new Configuration();

RequestedOverrideConfiguration仅有一处赋值:ConfigurationContainer.onRequestedOverrideConfigurationChanged,稍后会介绍到

Step 8. DisplayContent.updateOrientation

/**     * Update orientation of the display, returning a non-null new Configuration if it has     * changed from the current orientation. If a non-null configuration is returned, someone must     * call {@link WindowManagerService#setNewDisplayOverrideConfiguration(Configuration,     * DisplayContent)} to tell the window manager it can unfreeze the screen. This will typically     * be done by calling {@link #sendNewConfiguration}.注释同样写得很详细了: 更新display的方向,如果方向更改则返回一个非null的新的config对象.                                                         如果返回了非null的config,则必须调用WindowManagerService. setNewDisplayOverrideConfiguration告诉wm,告诉他可以解冻了。     *     * @param currentConfig The current requested override configuration (it is usually set from     *                      the last {@link #sendNewConfiguration}) of the display. It is used to     *                      check if the configuration container has the latest state.     * @param freezeDisplayToken Freeze the app window token if the orientation is changed.     * @param forceUpdate See {@link DisplayRotation#updateRotationUnchecked(boolean)}     */    Configuration updateOrientation(Configuration currentConfig, IBinder freezeDisplayToken,            boolean forceUpdate) {
if (!mDisplayReady) {
return null; } Configuration config = null; // 判断是否更新方向,执行冻屏等操作,如果方向 change,则return true。否则,return false if (updateOrientation(forceUpdate)) {
// 关键函数: Step 9 -- Step 17 // If we changed the orientation but mOrientationChangeComplete is already true, // we used seamless rotation, and we don't need to freeze the screen. if (freezeDisplayToken != null && !mWmService.mRoot.mOrientationChangeComplete) {
// 本文这种情况下并不满足 final ActivityRecord activity = getActivityRecord(freezeDisplayToken); if (activity != null) {
activity.startFreezingScreen(); } } config = new Configuration(); // Compute display configuration based on display properties and policy settings. 如注释所示,就是基于display properties和policy setting计算display config和displayInfo Step 18 -- Step 22 computeScreenConfiguration(config); } else if (currentConfig != null) {
// No obvious action we need to take, but if our current state mismatches the // activity manager's, update it, disregarding font scale, which should remain set // to the value of the previous configuration. // Here we're calling Configuration#unset() instead of setToDefaults() because we // need to keep override configs clear of non-empty values (e.g. fontSize). mTmpConfiguration.unset(); mTmpConfiguration.updateFrom(currentConfig); computeScreenConfiguration(mTmpConfiguration); // 先计算config if (currentConfig.diff(mTmpConfiguration) != 0) {
// 方向未更改的情况下,config change, 同样冻屏 mWaitingForConfig = true; setLayoutNeeded(); mDisplayRotation.prepareNormalRotationAnimation(); config = new Configuration(mTmpConfiguration); } } return config; }

Step 9. DisplayContent.updateOrientation

private boolean updateOrientation(boolean forceUpdate) {
// 综合各种情况,返回最合适的方向值。 这个要综合layer排序,窗口的方向值做判断。 假设设置方向的activity是top resumed activity,则一般会返回刚才设置的mOrientation Step 10
final int orientation = getOrientation();
// The last orientation source is valid only after getOrientation.
final WindowContainer orientationSource = getLastOrientationSource();
final ActivityRecord r =
orientationSource != null ? orientationSource.asActivityRecord() : null;
if (r != null) {
final Task task = r.getTask();
if (task != null && orientation != task.mLastReportedRequestedOrientation) {
task.mLastReportedRequestedOrientation = orientation;
mAtmService.getTaskChangeNotificationController()
.notifyTaskRequestedOrientationChanged(task.mTaskId, orientation);
}
// Currently there is no use case from non-activity. 本文所说的这种情况下,返回false,暂不考虑,后面梳理其他情况下再介绍
if (handleTopActivityLaunchingInDifferentOrientation(r, true /* checkOpening */)) {
// Display orientation should be deferred until the top fixed rotation is finished.
return false;
}
}
// DisplayRotation: 构造函数中创建,与DisplayContent一对一 Step 11
return mDisplayRotation.updateOrientation(orientation, forceUpdate);
}

Step 10. DisplayContent.getOrientation

11.0的树形结构还没梳理,稍后补充上。 但是应该和10.0差不了多少

Step 11. DisplayRotation.updateOrientation

/**

  • Defines the mapping between orientation and rotation of a display.

  • Non-public methods are assumed to run inside WM lock.

    */
    public class DisplayRotation {}

    boolean updateOrientation(@ScreenOrientation int newOrientation, boolean forceUpdate) {

    // 如果新返回的方向值与之前一致,且未强制更新的情况下,则return
    if (newOrientation == mLastOrientation && !forceUpdate) {
    return false;
    }
    mLastOrientation = newOrientation; // 仅在此处赋值 指向AcitivityInfo.ScreenOrientation类型
    if (newOrientation != mCurrentAppOrientation) {
    mCurrentAppOrientation = newOrientation;
    if (isDefaultDisplay) {
    updateOrientationListenerLw(); // 更新方向监听器, 判断enable/disable 方向监听器
    }
    }
    return updateRotationUnchecked(forceUpdate); // Step 12 继续判断是否更新方向
    }

Step 12. DisplayRotation.updateRotationUnchecked

/**
* Update rotation with an option to force the update. This updates the container’s perception
* of rotation and, depending on the top activities, will freeze the screen or start seamless
* rotation. The display itself gets rotated in {@link DisplayContent#applyRotationLocked}
* during {@link DisplayContent#sendNewConfiguration}.
*
* @param forceUpdate Force the rotation update. Sometimes in WM we might skip updating
* orientation because we’re waiting for some rotation to finish or display
* to unfreeze, which results in configuration of the previously visible
* activity being applied to a newly visible one. Forcing the rotation
* update allows to workaround this issue.
* @return {@code true} if the rotation has been changed. In this case YOU MUST CALL
* {@link DisplayContent#sendNewConfiguration} TO COMPLETE THE ROTATION AND UNFREEZE
* THE SCREEN.
*/
boolean updateRotationUnchecked(boolean forceUpdate) {
final int displayId = mDisplayContent.getDisplayId();
if (!forceUpdate) { // 在非强制更新的情况下,有可能因为正在执行屏幕旋转动画,冻屏等因素,直接返回
if (mDeferredRotationPauseCount > 0) {
// Rotation updates have been paused temporarily. Defer the update until updates
// have been resumed.
ProtoLog.v(WM_DEBUG_ORIENTATION, “Deferring rotation, rotation is paused.”);
return false;
}

final ScreenRotationAnimation screenRotationAnimation =                mDisplayContent.getRotationAnimation();        if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {            // Rotation updates cannot be performed while the previous rotation change animation            // is still in progress. Skip this update. We will try updating again after the            // animation is finished and the display is unfrozen.            ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, animation in progress.");            return false;        }        if (mService.mDisplayFrozen) {            // Even if the screen rotation animation has finished (e.g. isAnimating returns            // false), there is still some time where we haven't yet unfrozen the display. We            // also need to abort rotation here.            ProtoLog.v(WM_DEBUG_ORIENTATION,                    "Deferring rotation, still finishing previous rotation");            return false;        }        if (mDisplayContent.mFixedRotationTransitionListener                .isTopFixedOrientationRecentsAnimating()) {            // During the recents animation, the closing app might still be considered on top.            // In order to ignore its requested orientation to avoid a sensor led rotation (e.g            // user rotating the device while the recents animation is running), we ignore            // rotation update while the animation is running.            return false;        }    }    if (!mService.mDisplayEnabled) {        // No point choosing a rotation if the display is not enabled.        ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, display is not enabled.");        return false;    }    final int oldRotation = mRotation;   // mRotation:Current rotation of the display.  display真正的方向  Surface.Rotation类型    final int lastOrientation = mLastOrientation;     // 保存新计算出来的mLastOrientation    //  综合sensor,rotation loack和docking mode等因素计算出合适的方向    final int rotation = rotationForOrientation(lastOrientation, oldRotation);    ProtoLog.v(WM_DEBUG_ORIENTATION,            "Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "                    + "oldRotation=%s (%d)",            Surface.rotationToString(rotation), rotation,            displayId,            ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,            Surface.rotationToString(oldRotation), oldRotation);    ProtoLog.v(WM_DEBUG_ORIENTATION,            "Display id=%d selected orientation %s (%d), got rotation %s (%d)", displayId,            ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,            Surface.rotationToString(rotation), rotation);    if (oldRotation == rotation) {    // 如果新计算出的方向与之前的方向一致,则return        // No change.        return false;    }    ProtoLog.v(WM_DEBUG_ORIENTATION,            "Display id=%d rotation changed to %d from %d, lastOrientation=%d",                    displayId, rotation, oldRotation, lastOrientation);    // int delta = newRotation - oldRotation;    // if (delta < 0) delta += 4;   0:0   90:1  180:2  270:3    if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) {   // 不是180度更改        // 表明系统config change,直到sent complete config才设置为false        mDisplayContent.mWaitingForConfig = true;    }    mRotation = rotation;   // 更新系统方向    mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;    // send freeze timeout msg    mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,            mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);    mDisplayContent.setLayoutNeeded();    // 允许窗口执行performLayout, 即计算新的窗口位置    // 判断旋转方式,后面一篇文章,我会梳理另一种更新方向的情况,设置fixedApp,就是samless rotate,  即不执行屏幕旋转动画和冻屏等操作    if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {        // The screen rotation animation uses a screenshot to freeze the screen while windows        // resize underneath. When we are rotating seamlessly, we allow the elements to        // transition to their rotated state independently and without a freeze required.        prepareSeamlessRotation();    } else {        // 本文这种情况,执行旋转动画,冻屏等操作  Step 13-Step 17  这一步执行冻屏,截屏并显示        prepareNormalRotationAnimation();    }    // Give a remote handler (system ui) some time to reposition things.    startRemoteRotation(oldRotation, mRotation);    return true;}

Step 13. DisplayRotation.prepareNormalRotationAnimation

void prepareNormalRotationAnimation() {
cancelSeamlessRotation();
// 挑选合适的旋转动画,但是一般都是null,最后由ScreenRotationAnimation根据detla决定
final RotationAnimationPair anim = selectRotationAnimation();
mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent); // 开始冻屏
}

Step 14. WindowManagerService.startFreezingDisplay

void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent) {
startFreezingDisplay(exitAnim, enterAnim, displayContent,
ROTATION_UNDEFINED /* overrideOriginalRotation */);
}

void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,        int overrideOriginalRotation) {    // 如果此时已经处于冻屏状态或者是seamless rotation,则return    if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {        return;    }    if (!displayContent.isReady() || !mPolicy.isScreenOn() || !displayContent.okToAnimate()) {        // No need to freeze the screen before the display is ready,  if the screen is off,        // or we can't currently animate.  判断是否满足冻屏的条件        return;    }    ProtoLog.d(WM_DEBUG_ORIENTATION,                        "startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s",                        exitAnim, enterAnim, Debug.getCallers(8));    mScreenFrozenLock.acquire();    // 避免灭屏    mDisplayFrozen = true;    // 标记系统已处于冻屏状态    mDisplayFreezeTime = SystemClock.elapsedRealtime();    mLastFinishedFreezeSource = null;    // {@link mDisplayFrozen} prevents us from freezing on multiple displays at the same time.    // As a result, we only track the display that has initially froze the screen.    mFrozenDisplayId = displayContent.getDisplayId();    // 冻结input,其实原理很简单,就是设置值到InputDispatcher中,让其不再分发而已    mInputManagerCallback.freezeInputDispatchingLw();    if (displayContent.mAppTransition.isTransitionSet()) {        displayContent.mAppTransition.freeze();     // app transition—activity切换时执行的动画    }    if (PROFILE_ORIENTATION) {        File file = new File("/data/system/frozen");        Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);    }    mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);    mExitAnimId = exitAnim;    mEnterAnimId = enterAnim;    displayContent.updateDisplayInfo();    // 如果并没有指定方向,则从DisplayInfo中获取方向,注意此时系统config以及displayInfo尚未更新,因此返回的是之前的方向值,这里值异常的话,会导致旋转动画异常,等会儿详细分析    final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED            ? overrideOriginalRotation            : displayContent.getDisplayInfo().rotation;    // new ScreenRotationAnimation,并将其保存到DisplayContent中   Step 15-Step17    displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,            originalRotation));}

Step 15. ScreenRotationAnimation.ScreenRotationAnimation

// This class handles the rotation animation when the device is rotated. 这个类用于处理设备旋转时的rotation animation.
class ScreenRotationAnimation {}
ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
mService = displayContent.mWmService;
mContext = mService.mContext;
mDisplayContent = displayContent;
displayContent.getBounds(mOriginalDisplayRect);

// Screenshot does NOT include rotation!    final DisplayInfo displayInfo = displayContent.getDisplayInfo();    // 又重新从DisplayInfo中获取原方向值,因为之前传进来的参数可能由overrideOriginalRotation指定, 这个值是真正决定解冻前,截图的显示位置    final int realOriginalRotation = displayInfo.rotation;    final int originalWidth;    final int originalHeight;    if (displayContent.getDisplayRotation().isFixedToUserRotation()) {        // Emulated orientation.        mForceDefaultOrientation = true;        originalWidth = displayContent.mBaseDisplayWidth;        originalHeight = displayContent.mBaseDisplayHeight;    } else {        // Normal situation        originalWidth = displayInfo.logicalWidth;        originalHeight = displayInfo.logicalHeight;    }    // 获取宽度和高度,这个也比较好理解,以90->0度为例,此时DisplayInfo还是横屏下的数据,    // 因此width为2400,height为1080(只是以分辨率为1080*2400举例), 那么此时计算出来的mWidth为1080,mHeight为2400    if (realOriginalRotation == Surface.ROTATION_90            || realOriginalRotation == Surface.ROTATION_270) {        mWidth = originalHeight;        mHeight = originalWidth;    } else {        mWidth = originalWidth;        mHeight = originalHeight;    }    mOriginalRotation = originalRotation;     // 保存前面传进来的参数,这个用于决定旋转动画    // If the delta is not zero, the rotation of display may not change, but we still want to    // apply rotation animation because there should be a top app shown as rotated. So the    // specified original rotation customizes the direction of animation to have better look    // when restoring the rotated app to the same rotation as current display.    final int delta = DisplayContent.deltaRotation(originalRotation, realOriginalRotation);    final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270;    mOriginalWidth = flipped ? originalHeight : originalWidth;    mOriginalHeight = flipped ? originalWidth : originalHeight;    // Utility class that runs a {@link ScreenRotationAnimation} on the {@link SurfaceAnimationRunner}.    10.0的旋转动画还运行在android.anim线程中    mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();    // Check whether the current screen contains any secure content.    final boolean isSecure = displayContent.hasSecureWindowOnScreen();    final SurfaceControl.Transaction t = mService.mTransactionFactory.get();    try {        mBackColorSurface = displayContent.makeChildSurface(null)                .setName("BackColorSurface")                .setColorLayer()                .setCallsite("ScreenRotationAnimation")                .build();        mScreenshotLayer = displayContent.makeOverlay()                .setName("RotationLayer")                .setBufferSize(mWidth, mHeight)                .setSecure(isSecure)                .setCallsite("ScreenRotationAnimation")                .build();     // 这个就是ScreenShot suface,创建Surface相关,以后再详细写下吧        mEnterBlackFrameLayer = displayContent.makeOverlay()                .setName("EnterBlackFrameLayer")                .setContainerLayer()                .setCallsite("ScreenRotationAnimation")                .build();        // In case display bounds change, screenshot buffer and surface may mismatch so set a        // scaling mode.        SurfaceControl.Transaction t2 = mService.mTransactionFactory.get();        t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW);        t2.apply(true /* sync */);        // Capture a screenshot into the surface we just created.        // 下面那么多其实就是抓取一个截图绑定到surface上        final int displayId = displayContent.getDisplayId();        final Surface surface = mService.mSurfaceFactory.get();        surface.copyFrom(mScreenshotLayer);        SurfaceControl.ScreenshotGraphicBuffer gb =                mService.mDisplayManagerInternal.systemScreenshot(displayId);        if (gb != null) {            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,                    "ScreenRotationAnimation#getMedianBorderLuma");            mStartLuma = RotationAnimationUtils.getMedianBorderLuma(gb.getGraphicBuffer(),                    gb.getColorSpace());            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);            try {                surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(),                        gb.getColorSpace());            } catch (RuntimeException e) {                Slog.w(TAG, "Failed to attach screenshot - " + e.getMessage());            }            // If the screenshot contains secure layers, we have to make sure the            // screenshot surface we display it in also has FLAG_SECURE so that            // the user can not screenshot secure layers via the screenshot surface.            if (gb.containsSecureLayers()) {                t.setSecure(mScreenshotLayer, true);            }            t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);            t.reparent(mBackColorSurface, displayContent.getSurfaceControl());            t.setLayer(mBackColorSurface, -1);            t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});            t.setAlpha(mBackColorSurface, 1);            t.show(mScreenshotLayer);            t.show(mBackColorSurface);        } else {            Slog.w(TAG, "Unable to take screenshot of display " + displayId);        }        surface.destroy();    } catch (OutOfResourcesException e) {        Slog.w(TAG, "Unable to allocate freeze surface", e);    }    ProtoLog.i(WM_SHOW_SURFACE_ALLOC,                "  FREEZE %s: CREATE", mScreenshotLayer);    setRotation(t, realOriginalRotation);    // 设置RotationLayer的初始位置    t.apply();    //  立即显示RotationLayer}

Step 16. ScreenRotationAnimation.setRotation

public void setRotation(SurfaceControl.Transaction t, int rotation) {
mCurRotation = rotation; // 暂时没啥用,后面执行config更新时,会再一次设置新的方向值

// Compute the transformation matrix that must be applied    // to the snapshot to make it stay in the same original position    // with the current screen rotation.    计算原方向到0的delta    int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0);    RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);   // 根据detla,mWidth,mHeight计算对应的Matrix    setRotationTransform(t, mSnapshotInitialMatrix);}

Step 17. RotationAnimationUtils.createRotationMatrix

public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) {
switch (rotation) {
case Surface.ROTATION_0:
outMatrix.reset();
break;
case Surface.ROTATION_90:
outMatrix.setRotate(90, 0, 0);
outMatrix.postTranslate(height, 0);
break;
case Surface.ROTATION_180:
outMatrix.setRotate(180, 0, 0);
outMatrix.postTranslate(width, height);
break;
case Surface.ROTATION_270:
outMatrix.setRotate(270, 0, 0);
outMatrix.postTranslate(0, width);
break;
}
}
这个我不知道你们有没有搞混,我之前是一直都不怎么理解。 这里简单介绍下: Matrix的主要用于坐标转换映射,我们可以通过 Matrix 矩阵来控制视图的变换。
比如以90->0的方向旋转为例,坐标原点都是左上角,此时穿进来的参数detla为3,即Surface.ROTATION_270,mWidth为1080,mHeight为2400。此时就得到了相应的变换。整个流程如下图,RotationLayer就得到了正确的坐标位置。

private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {    if (mScreenshotLayer == null) {        return;    }    matrix.getValues(mTmpFloats);    float x = mTmpFloats[Matrix.MTRANS_X];    float y = mTmpFloats[Matrix.MTRANS_Y];    if (mForceDefaultOrientation) {        mDisplayContent.getBounds(mCurrentDisplayRect);        x -= mCurrentDisplayRect.left;        y -= mCurrentDisplayRect.top;    }    t.setPosition(mScreenshotLayer, x, y);      // 上面的情况计算出来的: x:0 y:1080    t.setMatrix(mScreenshotLayer,            mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],            mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);    t.setAlpha(mScreenshotLayer, (float) 1.0);    t.show(mScreenshotLayer);    // 应用计算出来的Matrix, 以及设置alpha,并显示}

Step 9 – Step 17 主要的逻辑就是: 综合多种信息判断是否更新方向,若方向更新,则判断是否是seamless raotation,若是,则不执行冻屏等操作,直接reruen,否则执行冻屏,冻结input,截屏并显示。如果方向不更新,直接reutrn。

下一步就是基于更新后的方向重新计算DisplayInfo和Configuration。 Step 18 – Step 22

Step 18. DisplayContent.computeScreenConfiguration

/**
* Compute display configuration based on display properties and policy settings.
* Do not call if mDisplayReady == false.
*/
void computeScreenConfiguration(Configuration config) {
// 第一步就是根据最新信息,重新计算更新DisplayInfo Step 19 – Step 20
final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config);
calculateBounds(displayInfo, mTmpBounds); // Step 21 基于新的方向计算bounds
config.windowConfiguration.setBounds(mTmpBounds);
config.windowConfiguration.setWindowingMode(getWindowingMode());
config.windowConfiguration.setDisplayWindowingMode(getWindowingMode()); // 更新WindowConfiguration的相关信息,我们经常看到的config信息的起始设置点就是这里

final int dw = displayInfo.logicalWidth;    final int dh = displayInfo.logicalHeight;    computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation, config.uiMode,            displayInfo.displayCutout);    // Step 22 更新appBounds,方向等值    config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)            | ((displayInfo.flags & Display.FLAG_ROUND) != 0            ? Configuration.SCREENLAYOUT_ROUND_YES            : Configuration.SCREENLAYOUT_ROUND_NO);     // 下面就是更新Config的一些其他数据信息,以后遇到再看吧    config.densityDpi = displayInfo.logicalDensityDpi;    config.colorMode =            ((displayInfo.isHdr() && mWmService.hasHdrSupport())                    ? Configuration.COLOR_MODE_HDR_YES                    : Configuration.COLOR_MODE_HDR_NO)                    | (displayInfo.isWideColorGamut() && mWmService.hasWideColorGamutSupport()                    ? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES                    : Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO);    // Update the configuration based on available input devices, lid switch,    // and platform configuration.    config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;    config.keyboard = Configuration.KEYBOARD_NOKEYS;    config.navigation = Configuration.NAVIGATION_NONAV;    int keyboardPresence = 0;    int navigationPresence = 0;    final InputDevice[] devices = mWmService.mInputManager.getInputDevices();    final int len = devices != null ? devices.length : 0;    for (int i = 0; i < len; i++) {        InputDevice device = devices[i];        // Ignore virtual input device.        if (device.isVirtual()) {            continue;        }        // Check if input device can dispatch events to current display.        // If display type is virtual, will follow the default display.        if (!mWmService.mInputManager.canDispatchToDisplay(device.getId(),                displayInfo.type == Display.TYPE_VIRTUAL ? DEFAULT_DISPLAY : mDisplayId)) {            continue;        }        final int sources = device.getSources();        final int presenceFlag = device.isExternal()                ? WindowManagerPolicy.PRESENCE_EXTERNAL : WindowManagerPolicy.PRESENCE_INTERNAL;        if (mWmService.mIsTouchDevice) {            if ((sources & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) {                config.touchscreen = Configuration.TOUCHSCREEN_FINGER;            }        } else {            config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;        }        if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {            config.navigation = Configuration.NAVIGATION_TRACKBALL;            navigationPresence |= presenceFlag;        } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD                && config.navigation == Configuration.NAVIGATION_NONAV) {            config.navigation = Configuration.NAVIGATION_DPAD;            navigationPresence |= presenceFlag;        }        if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {            config.keyboard = Configuration.KEYBOARD_QWERTY;            keyboardPresence |= presenceFlag;        }    }    if (config.navigation == Configuration.NAVIGATION_NONAV && mWmService.mHasPermanentDpad) {        config.navigation = Configuration.NAVIGATION_DPAD;        navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL;    }    // Determine whether a hard keyboard is available and enabled.    // TODO(multi-display): Should the hardware keyboard be tied to a display or to a device?    boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;    if (hardKeyboardAvailable != mWmService.mHardKeyboardAvailable) {        mWmService.mHardKeyboardAvailable = hardKeyboardAvailable;        mWmService.mH.removeMessages(REPORT_HARD_KEYBOARD_STATUS_CHANGE);        mWmService.mH.sendEmptyMessage(REPORT_HARD_KEYBOARD_STATUS_CHANGE);    }    mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors();    // Let the policy update hidden states.    config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;    config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;    config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;    mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);}

Step 19. DisplayContent.updateDisplayAndOrientation

/**
* Update {@link #mDisplayInfo} and other internal variables when display is rotated or config
* changed.
* Do not call if {@link WindowManagerService#mDisplayReady} == false.
*/
private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) {
// Use the effective “visual” dimensions based on current rotation
final int rotation = getRotation(); // 获取的是DisplayRotation.mRotation 前面已经设置为新的目标方向
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
// Overridden display size. Initialized with {@link #mInitialDisplayWidth} and {@link #mInitialDisplayHeight}, but can be set via shell command “adb shell wm size”.
final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth; // 这两个值应该不会因为方向的更改而更改,但是具体我也没深入研究过
final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;

// Update application display metrics.       // DisplayCutout:Represents the area of the display that is not functional for displaying content. 其实就是非全屏的前置摄像头所在的区域,为了避免内容被前置摄像头遮盖    final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);    final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();    // appWidth = dw - nav bar width - (cutout safe left + cutout safe right)    final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,            displayCutout);    // appHeight计算也差不多,就不贴代码了 appHeight = dh - nav bar height - (cutout safe top + cutout safe bottom)    final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,            displayCutout);    mDisplayInfo.rotation = rotation;    // 下面就是更新DisplayInfo的方向,宽度,高度等值    mDisplayInfo.logicalWidth = dw;    mDisplayInfo.logicalHeight = dh;    mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity;    mDisplayInfo.appWidth = appWidth;    mDisplayInfo.appHeight = appHeight;    if (isDefaultDisplay) {        mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics,                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);    }    mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;    mDisplayInfo.getAppMetrics(mDisplayMetrics);    if (mDisplayScalingDisabled) {        mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;    } else {        mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;    }    computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, uiMode, dw, dh,            mDisplayMetrics.density, outConfig);    // We usually set the override info in DisplayManager so that we get consistent display    // metrics values when displays are changing and don't send out new values until WM is aware    // of them. However, we don't do this for displays that serve as containers for ActivityView    // because we don't want letter-/pillar-boxing during resize.    final DisplayInfo overrideDisplayInfo = mShouldOverrideDisplayConfiguration            ? mDisplayInfo : null;    mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,            overrideDisplayInfo);    mBaseDisplayRect.set(0, 0, dw, dh);    if (isDefaultDisplay) {        mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,                mCompatDisplayMetrics);    }    return mDisplayInfo;}

Step 20. DisplayPolicy.getNonDecorDisplayWidth

/**
* Return the display width available after excluding any screen
* decorations that could never be removed in Honeycomb. That is, system bar or
* button bar.
*/
public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
DisplayCutout displayCutout) {
int width = fullWidth;
// mHasNavigationBar: DisplayPolciy构造函数中赋值,DisplayPolicy是在DisplayContent构造函数中创建的,一对一。 所以应用设置显示或隐藏nav bar,并不会对该值造成影响
if (hasNavigationBar()) {
final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation);
if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) {
width -= getNavigationBarWidth(rotation, uiMode);
}
}

// 而DisplayCutout的区域是由config_mainBuiltInDisplayCutoutRectApproximation提供的数据决定的,不同的机型,数据都会不一样    if (displayCutout != null) {        width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight();    }    return width;}

DisplayPolicy构造函数中: mHasNavigationBar的赋值逻辑

if (mDisplayContent.isDefaultDisplay) {
mHasStatusBar = true;
mHasNavigationBar = mContext.getResources().getBoolean(R.bool.config_showNavigationBar);

// Allow a system property to override this. Used by the emulator.        // See also hasNavigationBar().        String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");        if ("1".equals(navBarOverride)) {            mHasNavigationBar = false;        } else if ("0".equals(navBarOverride)) {            mHasNavigationBar = true;        }    } else {        mHasStatusBar = false;        mHasNavigationBar = mDisplayContent.supportsSystemDecorations();    }

Step 21. DisplayContent.calculateBounds

// Determines the current display bounds based on the current state
private void calculateBounds(DisplayInfo displayInfo, Rect out) {
// Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
final int rotation = displayInfo.rotation;
boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
int width = displayInfo.logicalWidth;
int left = (physWidth - width) / 2;
int height = displayInfo.logicalHeight;
int top = (physHeight - height) / 2;
// 基于新的方向计算outBounds,以本文的情况为例,out为(0,0–1080,22400)
out.set(left, top, left + width, top + height);
}

Step 22. DisplayContent.computeScreenAppConfiguration

/** Compute configuration related to application without changing current display. /
private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh,
int rotation, int uiMode, DisplayCutout displayCutout) {
final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
displayCutout);
final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,
displayCutout); // appWidth和appHeight的计算逻辑已经分析过了
mDisplayPolicy.getNonDecorInsetsLw(rotation, dw, dh, displayCutout, mTmpRect); // 这里就是根据nav bar和displaycutout的坐标,计算出inset区域,逻辑就不贴了
final int leftInset = mTmpRect.left;
final int topInset = mTmpRect.top;
// AppBounds at the root level should mirror the app screen size.
outConfig.windowConfiguration.setAppBounds(leftInset /
left /, topInset / top /,
leftInset + appWidth /
right /, topInset + appHeight / bottom */); // 更新appBounds区域
outConfig.windowConfiguration.setRotation(rotation); // 更新方向,density等
outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;

final float density = mDisplayMetrics.density;    outConfig.screenWidthDp = (int) (mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation,            uiMode, displayCutout) / density);    outConfig.screenHeightDp = (int) (mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation,            uiMode, displayCutout) / density);    outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale);    outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);    final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);    outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw,            dh);}

转载地址:https://cczheng.blog.csdn.net/article/details/115770155 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:MTK 9.0平台调试 gsensor
下一篇:Android10.0 OTA 错误解决办法(@/cache/recovery/block.map‘ failed)

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年04月08日 21时19分39秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

领导让我整理上个季度的销售额,幸好我会Python数据分析,你猜我几点下班 2019-04-29
【Python爬虫实战】为何如此痴迷Python?还不是因为爱看小姐姐图 2019-04-29
2021年6月全国程序员薪资出炉,大佬您上榜了吗? 2019-04-29
零基础自学Python,你也可以实现经济独立! 2019-04-29
ElasticSearch与Mysql对比(ElasticSearch常用方法大全,持续更新) 2019-04-29
数字化转型的主干道上,华为云以“三大关键”成企业智能化推手 2019-04-29
数字化为何不走“捷”“径”? 2019-04-29
和总裁、专家交朋友,华为云助推政企智能化升级又做到前面去了 2019-04-29
BCOP章鱼船长,6月22日晚上8点上线薄饼 2019-04-29
为战疫助力,半导体功不可没 2019-04-29
了解这些操作,Python中99%的文件操作都将变得游刃有余! 2019-04-29
知道如何操作还不够!深入了解4大热门机器学习算法 2019-04-29
只有经历过,才能深刻理解的9个编程道理 2019-04-29
发现超能力:这些数据科学技能助你更高效专业 2019-04-29
AI当道,人工智能将如何改变金融业? 2019-04-29
消除性别成见,技术领域需要更多“乘风破浪的姐姐” 2019-04-29
7行代码击败整个金融业,这对20多岁的爱尔兰兄弟是如何做到的? 2019-04-29
2020十大编程博客:私藏的宝藏编程语言博客大放送! 2019-04-29
编程中的角色选择:哪类工作角色最适合你? 2019-04-29
10种算法一文打尽!基本图表算法的视觉化阐释 2019-04-29