【Android 13源码分析】屏幕旋转-3

忽然有一天,我想要做一件事:去代码中去验证那些曾经被"灌输"的理论。

-- 服装学院的IT男【Android 13源码分析】

【Android 13源码分析】屏幕旋转-1

【Android 13源码分析】屏幕旋转-2

【Android 13源码分析】屏幕旋转-3

本篇为屏幕旋转的第三篇,主要介绍屏幕旋转动画相关。 内容相对于前面会多一些。

既然是动画肯定要创建 leash 图层,加上日志后发现下面的堆栈:

php 复制代码
04-21 16:11:08.317  6355  6377 D WindowManager: Animation start for com.android.server.wm.SimpleSurfaceAnimatable@4e656fa, anim=android.view.animation.AnimationSet@dd13ea1
04-21 16:11:08.317  6355  6377 I WindowManager: Cancelling animation restarting=true for com.android.server.wm.SimpleSurfaceAnimatable@4b2e8c6
04-21 16:11:08.317  6355  6377 E biubiubiu: SurfaceAnimator  createAnimationLeash: screen_rotation
04-21 16:11:08.317  6355  6377 E biubiubiu: java.lang.Exception
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.SurfaceAnimator.createAnimationLeash(SurfaceAnimator.java:458)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.SurfaceAnimator.startAnimation(SurfaceAnimator.java:184)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.SurfaceAnimator.startAnimation(SurfaceAnimator.java:213)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.ScreenRotationAnimation$SurfaceRotationAnimationController.startAnimation(ScreenRotationAnimation.java:708)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.ScreenRotationAnimation$SurfaceRotationAnimationController.startScreenshotRotationAnimation(ScreenRotationAnimation.java:623)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.ScreenRotationAnimation$SurfaceRotationAnimationController.startScreenRotationAnimation(ScreenRotationAnimation.java:575)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.ScreenRotationAnimation.startAnimation(ScreenRotationAnimation.java:430)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.ScreenRotationAnimation.dismiss(ScreenRotationAnimation.java:448)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.WindowManagerService.doStopFreezingDisplayLocked(WindowManagerService.java:6250)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.WindowManagerService.stopFreezingDisplayLocked(WindowManagerService.java:6209)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:895)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:788)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:178)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:126)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:115)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer$Traverser.run(WindowSurfacePlacer.java:57)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at android.os.Handler.handleCallback(Handler.java:942)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at android.os.Handler.dispatchMessage(Handler.java:99)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at android.os.Looper.loopOnce(Looper.java:210)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at android.os.Looper.loop(Looper.java:297)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at android.os.HandlerThread.run(HandlerThread.java:67)
04-21 16:11:08.317  6355  6377 E biubiubiu: 	at com.android.server.ServiceThread.run(ServiceThread.java:44)
04-21 16:11:08.317  6355  6377 I WindowManager: Reparenting to leash for com.android.server.wm.SimpleSurfaceAnimatable@4b2e8c6

通过上面的堆栈知道下面2个信息:

    1. 触发流程就是一次 layout
    1. 真正的动画逻辑都在 WindowManagerService::doStopFreezingDisplayLocked 方法之后,也就是屏幕解冻后。

layout 调用频率很高,但肯定不会每次都执行旋转动画,所以先看一下执行动画前的逻辑中,有哪些条件进行控制。

1. 旋转动画前的流程处理

scss 复制代码
# RootWindowContainer

    // During an orientation change, we track whether all windows have rendered
    // at the new orientation, and this will be false from changing orientation until that occurs.
    // For seamless rotation cases this always stays true, as the windows complete their orientation
    // changes 1 by 1 without disturbing global state.
    // 旋转后的改变是否完成
    boolean mOrientationChangeComplete = true;

    void performSurfacePlacementNoTrace() {
        ......
        // 开启事务
        mWmService.openSurfaceTransaction();
        try {
            // 处理事务
            applySurfaceChangesTransaction();
        } catch (RuntimeException e) {
            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
        } finally {
            // 关闭事务。重点* 内部会触发apply, 才是真正将surface提交到SurfaceFlinger
            mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        ......
        // app事务
        checkAppTransitionReady(surfacePlacer);
        ......
        // 打印 mOrientationChangeComplete 的值
        if (mWmService.mDisplayFrozen) {
            ProtoLog.v(WM_DEBUG_ORIENTATION,
                    "With display frozen, orientationChangeComplete=%b",
                    mOrientationChangeComplete);
        }
        // 重点* 解冻,旋转动画
        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 。刚好上面也有打印,看问题的时候可以搜这个日志。 不过搜到发现会多次打印,当然最后一次是 orientationChangeComplete=true 。

ini 复制代码
    Line 43328: 04-30 19:12:02.215 25196 26663 V WindowManager: With display frozen, orientationChangeComplete=false
	Line 25089: 04-30 19:12:02.011 25196 25364 V WindowManager: With display frozen, orientationChangeComplete=true
	Line 25156: 04-30 19:12:02.014 25196 25358 V WindowManager: With display frozen, orientationChangeComplete=true
	Line 25455: 04-30 19:12:02.027 25196 25364 V WindowManager: With display frozen, orientationChangeComplete=true
	Line 30269: 04-30 19:12:02.115 25196 25358 V WindowManager: With display frozen, orientationChangeComplete=true
	Line 32228: 04-30 19:12:02.143 25196 25358 V WindowManager: With display frozen, orientationChangeComplete=true
	Line 43328: 04-30 19:12:02.215 25196 26663 V WindowManager: With display frozen, orientationChangeComplete=false
	Line 57417: 04-30 19:12:02.290 25196 25364 V WindowManager: With display frozen, orientationChangeComplete=false
	Line 57713: 04-30 19:12:02.296 25196 25364 V WindowManager: With display frozen, orientationChangeComplete=false
	Line 59140: 04-30 19:12:02.311 25196 25364 V WindowManager: With display frozen, orientationChangeComplete=true

可以看到这个值会一直改变,所以先来了解一下这个变量。

1.1 mOrientationChangeComplete 相关

这边变量默认为 true,那肯定不是每次都能执行进来。

根据注释理解,屏幕旋转后,窗口需要重绘,所以用 mOrientationChangeComplete 变量来控制是否已经重绘完成。

1.1.1 设置true

先看一下 设置为 true 的地方。

WindowAnimator 根据注释是专门处理窗口Surface 和 动画的类,全局唯一,并且是定义在 WindowManagerService 下的一个成员变量。

arduino 复制代码
# WindowManagerService
    final WindowAnimator mAnimator;
    
    private WindowManagerService(Context context, InputManagerService inputManager,....){
        ......
        mAnimator = new WindowAnimator(this);
        ......
    }

看一下构造方法

ini 复制代码
# WindowAnimator
    WindowAnimator(final WindowManagerService service) {
        mService = service;
        ......
        mAnimationFrameCallback = frameTimeNs -> {
            synchronized (mService.mGlobalLock) {
                mAnimationFrameCallbackScheduled = false;
                final long vsyncId = mChoreographer.getVsyncId();
                // Trace
                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmAnimate");
               
                // 每次 Vsync 信号来临的时候都执行
                animate(frameTimeNs, vsyncId);
                /// M: add systrace @{
                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                ......
            }
        };
    }
    private void animate(long frameTimeNs, long vsyncId) {
        if (!mInitialized) {
            return;
        }
        ......
        final RootWindowContainer root = mService.mRoot;
        ......
        // 设置为true
        root.mOrientationChangeComplete = true;
        // 日志
        ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
        ......
            for (int i = 0; i < numDisplays; i++) {
                final int displayId = mDisplayContentsAnimators.keyAt(i);
                final DisplayContent dc = root.getDisplayContent(displayId);
                // 处理App相关事务
                dc.checkAppWindowsReadyToShow();
                ......
            }
        ......
    }

WindowAnimator::animate 方法是做了很多事的,其他的先不管,目前看到只要来了 Vsync 就会把 mOrientationChangeComplete 设置为 true 。

1.1.2 设置false

再看一下什么情况下会被设置 false 。

csharp 复制代码
# WindowStateAnimator
    void prepareSurfaceLocked(SurfaceControl.Transaction t) {
        ......
        // 没有surface就退出
        if (!hasSurface()) {
            ......
            return;
        }
        ......
        // 条件满足会提交Surface提交到SurfaceFlinger 
        if (showSurfaceRobustlyLocked(t)) {
            ......      
        }
        ......
        // 如果窗口方向改变
        if (w.getOrientationChanging()) {
            // 如果还没绘制完
            if (!w.isDrawn()) {
                // 是否需要同步旋转改变(应用窗口一般都满足条件)
                if (w.mDisplayContent.shouldSyncRotationChange(w)) {
                    android.util.Log.d("biubiubiu", "  mLastWindowFreezeSource: "+w);
                    // 设置false
                    w.mWmService.mRoot.mOrientationChangeComplete = false;
                    // 重点* 记录最后是哪个窗口设置的需要冻屏
                    mAnimator.mLastWindowFreezeSource = w;
                }
                ProtoLog.v(WM_DEBUG_ORIENTATION,
                        "Orientation continue waiting for draw in %s", w);
            } else {
                // 已经完成了改变
                w.setOrientationChanging(false);
                ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change complete in %s", w);
            }
        }
    }

这边我自己加了个日志, 输出如下:

scss 复制代码
    ......// 忽略一样的输出
	Line 42644: 04-30 19:12:02.213 25196 26663 D biubiubiu:   mLastWindowFreezeSource: Window{b3ff9ee u0 com.google.android.dialer/com.google.android.dialer.extensions.GoogleDialtactsActivity}
	Line 44360: 04-30 19:12:02.225 25196 25364 D biubiubiu:   mLastWindowFreezeSource: Window{b3ff9ee u0 com.google.android.dialer/com.google.android.dialer.extensions.GoogleDialtactsActivity}

这里如果要把 mOrientationChangeComplete 设置成 false 需要满足2个条件。

    1. WindowState::isDrawn 为false
    1. DisplayContent::shouldSyncRotationChange 为true,正常情况应用窗口都是同步的,所以为 true 。(状态栏导航栏窗口好像可以忽略)

其中第二个条件没啥问题,第一个看看代码

csharp 复制代码
# WindowState
    public boolean isDrawn() {
        return mHasSurface && !mDestroying &&
                (mWinAnimator.mDrawState == READY_TO_SHOW || mWinAnimator.mDrawState == HAS_DRAWN);
    } 

其实就是返回这个窗口是不是已经绘制完成了,READY_TO_SHOW 和 HAS_DRAWN 说明窗口的绘制信息提交到 SurfaceFlinger 绘制了。

1.1.3 小结

在主流程中需要 mOrientationChangeComplete 为 true 才能正常执行旋转的后续逻辑,暂且理解为后面的逻辑是执行旋转动画,然后把那个截图的图层移除。

那为了正常的显示,移除掉这个截图流程肯定是需要等底下的窗口都执行完了旋转切换逻辑,并且已经正常显示了才可以。 否则就可能出现黑屏的现象(这也是这个这个截图图层存在的意义)。

因此每一帧 Vsync 来的时候都把 mOrientationChangeComplete 设置为true ,然后再遍历窗口,是不是都绘制完了, 如果有一个窗口还没绘制完,就会把 mOrientationChangeComplete 设置成 false,并且记录下是哪个窗口导致不能进行后面的流程(屏幕解冻)。

1.2 ActivityRecord 解冻相关

当窗口绘制完成后 mOrientationChangeComplete = true ,就会继续走后续流程。

ini 复制代码
# WindowManagerService

    void stopFreezingDisplayLocked() {
        if (!mDisplayFrozen) {
            return;
        }
        // 获取默认屏幕
        final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
        final int numOpeningApps;
        final boolean waitingForConfig;
        // 是否正在等待远端旋转
        final boolean waitingForRemoteRotation;
        if (displayContent != null) {
            numOpeningApps = displayContent.mOpeningApps.size();
            waitingForConfig = displayContent.mWaitingForConfig;
            waitingForRemoteRotation =
                    displayContent.getDisplayRotation().isWaitingForRemoteRotation();
        } else {
            ......
        }
        // 重点* 1. 执行解冻(当前旋转动画)的逻辑
        if (waitingForConfig || waitingForRemoteRotation || mAppsFreezingScreen > 0
                || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
                || mClientFreezingScreen || numOpeningApps > 0) {
            ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
                    + "waitingForConfig=%b, waitingForRemoteRotation=%b, "
                    + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
                    + "mClientFreezingScreen=%b, mOpeningApps.size()=%d",
                    waitingForConfig, waitingForRemoteRotation,
                    mAppsFreezingScreen, mWindowsFreezingScreen,
                    mClientFreezingScreen, numOpeningApps);
            return;
        }
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStopFreezingDisplayLocked-"
                + mLastFinishedFreezeSource);
        // 重点* 2. 真正执行解冻逻辑 
        doStopFreezingDisplayLocked(displayContent);
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }

这里看到又有一堆条件的判断,不过下面有日志打印,如果发现异常可以根据日志看看是哪个变量导致的。

这里只关注2个变量

    1. waitingForRemoteRotation,这个看赋值是 DisplayRotation 下的 mIsWaitingForRemoteRotation 变量,之前分析的时候看到调用的远端就是 systemui ,不过上一篇分析的流程及是远端结束的 DisplayContent::sendNewConfiguration 流程,这个时候 mIsWaitingForRemoteRotation 已经是 false 了。
    1. mAppsFreezingScreen,这个变量是个 int 类型,在上一篇看到如果需要重启 Activity 会执行 startFreezingScreenLocked ,这个时候就是有一个 activity 要冻屏了,那么 mAppsFreezingScreen 的数量就+1。

第一个条件没啥问题,搜这块日志打印发现都是是 mAppsFreezingScreen=1 导致的 return 。

ini 复制代码
WindowManager: stopFreezingDisplayLocked: Returning waitingForConfig=false, waitingForRemoteRotation=false, mAppsFreezingScreen=1, mWindowsFreezingScreen=0, mClientFreezingScreen=false, mOpeningApps.size()=0

1.2.1 ActivityRecord解冻流程

mAppsFreezingScreen 这个int值减1的地方在 ActivityRecord::stopFreezingScreen 方法,也就是说这个方法会触发 Activity 的解冻。

解冻堆栈如下:

makefile 复制代码
stopFreezingScreen:6413, ActivityRecord (com.android.server.wm)
checkAppWindowsReadyToShow:6567, ActivityRecord (com.android.server.wm)
checkAppWindowsReadyToShow:1361, WindowContainer (com.android.server.wm)
checkAppWindowsReadyToShow:1361, WindowContainer (com.android.server.wm)
checkAppWindowsReadyToShow:1361, WindowContainer (com.android.server.wm)
checkAppWindowsReadyToShow:1361, WindowContainer (com.android.server.wm)
checkAppWindowsReadyToShow:1361, WindowContainer (com.android.server.wm)
checkAppWindowsReadyToShow:1361, WindowContainer (com.android.server.wm)
checkAppWindowsReadyToShow:1361, WindowContainer (com.android.server.wm)
animate:172, WindowAnimator (com.android.server.wm)
lambda$new$1$com-android-server-wm-WindowAnimator:109, WindowAnimator (com.android.server.wm)
doFrame:-1, WindowAnimator$$ExternalSyntheticLambda1 (com.android.server.wm)
run:1313, Choreographer$CallbackRecord (android.view)
run:1323, Choreographer$CallbackRecord (android.view)
doCallbacks:956, Choreographer (android.view)
doFrame:863, Choreographer (android.view)
run:1298, Choreographer$FrameDisplayEventReceiver (android.view)

可以看到这个方法的调用链和前面看到设置 mOrientationChangeComplete = true 的是一样的,都是当framework 层收到 Vsync 回调的时候。 再回顾一下 WindowAnimator::animate 方法

ini 复制代码
# WindowAnimator
    private void animate(long frameTimeNs, long vsyncId) {
        ......
        final RootWindowContainer root = mService.mRoot;
        ......
        // 设置为true
        root.mOrientationChangeComplete = true;
        // 日志
        ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
        ......
            for (int i = 0; i < numDisplays; i++) {
                final int displayId = mDisplayContentsAnimators.keyAt(i);
                final DisplayContent dc = root.getDisplayContent(displayId);
                // 处理App相关事务(条件满则触发解冻)
                dc.checkAppWindowsReadyToShow();
                ......
            }
        ......
    }
ini 复制代码
# WindowContainer
    void checkAppWindowsReadyToShow() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            // 对每个子容器在调用一下checkAppWindowsReadyToShow
            final WindowContainer wc = mChildren.get(i);
            wc.checkAppWindowsReadyToShow();
        }
    }

这里看到开始遍历子容器了,checkAppWindowsReadyToShow 方法也被子类重写,所以我们直接看 ActivityRecord 的实现即可。

csharp 复制代码
# ActivityRecord

    @Override
    void checkAppWindowsReadyToShow() {
        if (allDrawn == mLastAllDrawn) {
            return;
        }

        mLastAllDrawn = allDrawn;
        // 1. 所以窗口都绘制完成就为true
        if (!allDrawn) {
            return;
        }
        // 如果屏幕处于冻结状态
        if (mFreezingScreen) {
            // 2. 满足条件的窗口会设置成 HAS_DRAWN
            showAllWindowsLocked();
            // 3. 主流程* 解冻,注意2个参数
            stopFreezingScreen(false, true);
            // 4.1 日志 
            ProtoLog.i(WM_DEBUG_ORIENTATION,
                    "Setting mOrientationChangeComplete=true because wtoken %s "
                            + "numInteresting=%d numDrawn=%d",
                    this, mNumInterestingWindows, mNumDrawnWindows);
            // This will set mOrientationChangeComplete and cause a pass through layout.
            // 4.2 
            setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
                    "checkAppWindowsReadyToShow: freezingScreen");
        } else { ......}
    }

这里还是有很多需要注意的点的:

    1. 只有这个 ActivityRecord 下的所有窗口都绘制完成,才会执行后面的逻辑 。
    1. 这个方法内部会触发 WindowState.performShowLocked 方法,也就是将窗口状态设置为 HAS_DRAWN 。(发现另一个设置HAS_DRAWN的路径)
    1. 这个是主流程,等会要看的
    1. 这里是把 mOrientationChangeComplete设置为 true 的地方(可能会有疑问:上面小节不是看到设置true,怎么这里又变成true,稍后会解释)
    • 4.1 打印日志:mOrientationChangeComplete=true 和一些参数
    • 4.2 根据注释:这里将把 mOrientationChangeComplete 设置为true 日志如下:
ini 复制代码
04-30 19:12:02.303 25196 25364 I WindowManager: Setting mOrientationChangeComplete=true because wtoken ActivityRecord{5c18296 u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t27} numInteresting=1 numDrawn=0

先看一下解冻的方法

kotlin 复制代码
# ActivityRecord
    void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
        // 当前是为了解冻,如果没有冻屏,则不需要解冻
        if (!mFreezingScreen) {
            return;
        }
        // 日志
        ProtoLog.v(WM_DEBUG_ORIENTATION,
                "Clear freezing of %s force=%b", this, force);
        ......
        
        if (force || unfrozeWindows) {
            ProtoLog.v(WM_DEBUG_ORIENTATION, "No longer freezing: %s", this);
            mFreezingScreen = false;
            mWmService.unregisterAppFreezeListener(this);
            // 重点* 
            mWmService.mAppsFreezingScreen--;
            mWmService.mLastFinishedFreezeSource = this;
        }
        ......
    }

根据前面的分析,执行这个方法的时候参数 force = true ,所以会执行 " mWmService.mAppsFreezingScreen--; " ,本来是1,执行完后就是0了。 也就满足最初的分析,可以执行后续的流程了。

这块的调用链如下

arduino 复制代码
Choreographer$CallbackRecord::run
    WindowAnimator::animate
        WindowContainer::checkAppWindowsReadyToShow
            ActivityRecord::checkAppWindowsReadyToShow
                ActivityRecord::showAllWindowsLocked
                    WindowState.performShowLocked   -- 设置窗口状态为 HAS_DRAWN
                ActivityRecord::stopFreezingScreen  -- 解冻
                ActivityRecord::setAppLayoutChanges -- 触发一次layout

1.3 小结:ActivityRecord解冻,和mOrientationChangeComplete的关系

在 1.1 小节看到了 mOrientationChangeComplete 是怎么被设置为 true 和 false 的逻辑,知道他是用来控制当前旋转后,是不是有窗口没有绘制完的变量。

那可以先确定一个信息:窗口树的(需要同步改变的)WindowState绘制都完成了才会为true

但是这里之前看过日志,"With display frozen, orientationChangeComplete= XXX" 这个日志是频繁打印的,而且中间 true 和false的都有。这是为什么呢?

因为在屏幕旋转的时候,重启 Activity 了,那之前的窗口(WindowState)是要移除的,那在新的窗口(WindowState)添加进来之前,这段时间都是没有这个 Activity 对应的窗口(WindowState)的。自然这个期间执行到 WindowStateAnimator::prepareSurfaceLocked 时,窗口都不存在,所以肯定不会设置为 false,也就是保持在 WindowManagerService::animate 设置的值(true)。

而这段时间,ActivityRecord 是冻屏的,而且不满足解冻条件,所以这个期间的 mOrientationChangeComplete=true。并没什么用。

然后在 Activity 重启后,新的窗口(WindowState)挂载到窗口树后然后执行 layoutWindow, finishDrawing 等流程,执行完,这个时候 ActivityRecord 的窗口就绘制完成了,allDrawn 变量也就被设成成了 true。

在下一次 Vsync 信号来临时,条件满足,就会在 2.2 小节执行 ActivityRecord::checkAppWindowsReadyToShow 的时候就会把窗口状态设置成 HAS_DRAWN 。 并且执行后面的逻辑把 mAppsFreezingScreen 减1 。

然后触发layout,这个时候条件都满足了,后面就会顺利的执行到 WindowManagerService::doStopFreezingDisplayLocked 方法。 这里看一下关键逻辑的日志打印顺序,就能很清晰的理清逻辑顺序了。

2. 旋转动画逻辑

经过前面的分析,现在可以继续看 WindowManagerService::doStopFreezingDisplayLocked 方法了,后面的处理是和旋转动画相关的。

java 复制代码
# WindowManagerService
    private void doStopFreezingDisplayLocked(DisplayContent displayContent) {
        // 1. 真正解冻的日志
        ProtoLog.d(WM_DEBUG_ORIENTATION,
                    "stopFreezingDisplayLocked: Unfreezing now");
        ......
        // 2. 拿到之前保存在 DisplayContent 下的旋转动画
        ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
            : displayContent.getRotationAnimation();
        // 这个肯定满足的,不为空并且之前已经有截图图层了
        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
            // 3. 打印log 旋转动画消失的日志
            ProtoLog.i(WM_DEBUG_ORIENTATION, "**** Dismissing screen rotation animation");
            DisplayInfo displayInfo = displayContent.getDisplayInfo();
            ......
            // 主流程
            if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
                    getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
                        displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
                mTransaction.apply();
            } else {......}
        } else {{......}}
        ......
    }

在第一篇的时候知道 ScreenRotationAnimation 被保存到 DisplayContent 下面了,现在开始用到了。

后面就开始配置旋转动画需要的一些属性和成员了。

2.1 动画资源,相关类的创建

java 复制代码
# ScreenRotationAnimation
    // 动画是否开始
    private boolean mStarted;
    // 旋转动画控制类
    private SurfaceRotationAnimationController mSurfaceRotationAnimationController;

    public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration,
            float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
        ......
        // 前面肯定还没开始动画,所以为false
        if (!mStarted) {
            mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(),
                    mDisplayContent.getWindowingLayer());
            // 开始动画 
            startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
                    exitAnim, enterAnim);
        }
        if (!mStarted) {
            return false;
        }
        mFinishAnimReady = true;
        return true;
    }

    private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
            float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
        ......
        // 这次过来还是false
        if (mStarted) {
            return true;
        }
        // 开始动画
        mStarted = true;
        // Figure out how the screen has moved from the original rotation.
        // 旋转的角度 (mCurRotation=ROTATION_90 = 1, mOriginalRotation=ROTATION_0 = 0)
        int delta = deltaRotation(mCurRotation, mOriginalRotation);
        
        // 之前看过不是自定义动画,exitAnim 和 enterAnim 都是0
        final boolean customAnim;
        if (exitAnim != 0 && enterAnim != 0) {
            customAnim = true;
            mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
            mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
            mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
                    R.anim.screen_rotate_alpha);
        } else {
            // 默认走这
            customAnim = false;
            // 根据计算出来需要旋转的角度选择动画文件
            switch (delta) {
                ......
                // 横竖屏选择,根据日志的角度,delta计算结果为 3 = ROTATION_270
                case Surface.ROTATION_270:
                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
                            R.anim.screen_rotate_minus_90_exit);
                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
                            R.anim.screen_rotate_minus_90_enter);
                    break;
            }
        }
        // log
        ProtoLog.d(WM_DEBUG_ORIENTATION, "Start rotation animation. customAnim=%s, "
                        + "mCurRotation=%s, mOriginalRotation=%s",
                customAnim, Surface.rotationToString(mCurRotation),
                Surface.rotationToString(mOriginalRotation));
        ......
        if (customAnim) {
            mSurfaceRotationAnimationController.startCustomAnimation();
        } else {
            // 当前不是自定义动画,所以走这
            mSurfaceRotationAnimationController.startScreenRotationAnimation();
        }

        return true;
    }

这里的打印如下

ini 复制代码
04-21 16:11:08.315  6355  6377 D WindowManager: Start rotation animation. customAnim=false, mCurRotation=ROTATION_90, mOriginalRotation=ROTATION_0

最后会根据 customAnim 的值,选择对应的方法开始动画,当前分析常见 customAnim 为false

scss 复制代码
# ScreenRotationAnimation$SurfaceRotationAnimationController

        void startScreenRotationAnimation() {
            try {
                mService.mSurfaceAnimationRunner.deferStartingAnimations();
                // 开始旋转动画
                mDisplayAnimator = startDisplayRotation();
                mScreenshotRotationAnimator = startScreenshotRotationAnimation();
                // 开始颜色动画
                startColorAnimation();
            } finally {
                mService.mSurfaceAnimationRunner.continueStartingAnimations();
            }
        }

接着看 startDisplayRotation 方法

scss 复制代码
# ScreenRotationAnimation$SurfaceRotationAnimationController

    private final WindowManagerService mService;

        private SurfaceAnimator startDisplayRotation() {
            return startAnimation(initializeBuilder()
                            .setAnimationLeashParent(mDisplayContent.getSurfaceControl())
                            .setSurfaceControl(mDisplayContent.getWindowingLayer())
                            // 1. 父节点为DisplayContent
                            .setParentSurfaceControl(mDisplayContent.getSurfaceControl())
                            .setWidth(mDisplayContent.getSurfaceWidth())
                            .setHeight(mDisplayContent.getSurfaceHeight())
                            .build(),
                    // 2. 根据旋转进入动画构造一个 WindowAnimationSpec 对象
                    createWindowAnimationSpec(mRotateEnterAnimation),
                    this::onAnimationEnd);// 3. 动画结束回调
        }
        // 开始动画
        private SurfaceAnimator startAnimation(
                SurfaceAnimator.Animatable animatable,
                LocalAnimationAdapter.AnimationSpec animationSpec,
                OnAnimationFinishedCallback animationFinishedCallback) {
            // 构建 SurfaceAnimator
            SurfaceAnimator animator = new SurfaceAnimator(
                    animatable, animationFinishedCallback, mService);
            // 4. 本地动画Adapter
            LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
                    animationSpec, mService.mSurfaceAnimationRunner);
            // 开始动画
            animator.startAnimation(mDisplayContent.getPendingTransaction(),
                    localAnimationAdapter, false, ANIMATION_TYPE_SCREEN_ROTATION);
            return animator;
        }

这里信息还是有点多的:

    1. 旋转动画的 leash 是挂载到 DisplayContent 下的,所以它的层级很高
    1. 根据加载的动画资源,构建了一个 WindowAnimationSpec 对象
    1. 动画结束会执行 当前类的 onAnimationEnd 方法
    1. 是一个本地动画,也就是执行在 system_service 进程。

注意构建 LocalAnimationAdapter 的2个参数:

    1. 第一个参数 内部则有动画文件 mRotateEnterAnimation
csharp 复制代码
# ScreenRotationAnimation$SurfaceRotationAnimationController

        private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) {
            return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */,
                    false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */);
        }

mRotateEnterAnimation 这个变量被传递到了到了这个 WindowAnimationSpec 里面。

    1. 第二个参数 Runner 才是真正控制动画执行的地方,实际上就是WMS下的一个 SurfaceAnimationRunner 变量

下面就要开始动画逻辑了,根据对动画的了解,最后真正的执行处理还是会在 SurfaceAnimationRunner 下。

2.1 开始动画

动画的开始还是在 SurfaceAnimator::startAnimation

less 复制代码
# SurfaceAnimator
    private AnimationAdapter mAnimation;
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type) {
        startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */,
                null /* animationCancelledCallback */, null /* snapshotAnim */, null /* freezer */);
    }
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable Runnable animationCancelledCallback,
            @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
            cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
            mAnimation = anim;
            ......
            final SurfaceControl surface = mAnimatable.getSurfaceControl();
            ......
            mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
            if (mLeash == null) {
                // 重点* 创建leash图层
                mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                        mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                        0 /* y */, hidden, mService.mTransactionFactory);
                mAnimatable.onAnimationLeashCreated(t, mLeash);
            }
            ......
            // 重点* 开始动画主流程
            mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
            ......
    }

这里有2个点:

    1. 创建动画的 leash 图层
    1. 根据 mAnimation 的赋值,知道是 LocalAnimationAdapter ,当然最终控制动画的肯定是构建动画 Adapter 传进去的 AnimationRunner
less 复制代码
# LocalAnimationAdapter
    // 真正执行动画的Runner
    private final SurfaceAnimationRunner mAnimator;

    LocalAnimationAdapter(AnimationSpec spec, SurfaceAnimationRunner animator) {
        mSpec = spec;
        mAnimator = animator;
    }
    @Override
    public void startAnimation(SurfaceControl animationLeash, Transaction t,
            @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
        mAnimator.startAnimation(mSpec, animationLeash, t,
                () -> finishCallback.onAnimationFinished(type, this));
    }

内部又把执行动画的流程交给了 mAnimator ,而这个 mAnimator 之前看到提过就是WMS下的一个 SurfaceAnimationRunner 变量

scss 复制代码
# SurfaceAnimationRunner
    void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
            Runnable finishCallback) {

        synchronized (mLock) {
            // 动画文件,动画的leash图层,动画结束的回调,三个对象构建出一个runningAnim
            final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
                    finishCallback);
            boolean requiresEdgeExtension = requiresEdgeExtension(a);
            if (requiresEdgeExtension) {

            }

            if (!requiresEdgeExtension) {
                // 把动画添加到集合中
                mPendingAnimations.put(animationLeash, runningAnim);
                if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
                    // vsync 下一个来的时候则开始动画
                    mChoreographer.postFrameCallback(this::startAnimations);
                }

                // Some animations (e.g. move animations) require the initial transform to be
                // applied immediately.
                // 一些动画可能需要初始化 transform (暂时忽略)
                applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
            }
        }

    }

到这里,准备工作都处理完了,把动画对象放到了 mPendingAnimations 中,然后注册了一个回调到 Choreographer ,等下一帧 Vsync 信号来的时候,就会开始执行旋转动画了,也就是执行 SurfaceAnimationRunner::startAnimations 方法。 就会从 mPendingAnimations 取出对象做动画了。 然后从

3. 旋转动画真正的执行

其实动画真正的执行,很少会出现问题,这部分代码知道一下即可。

scss 复制代码
# SurfaceAnimationRunner
    private void startAnimations(long frameTimeNanos) {
        synchronized (mLock) {
            if (!mPreProcessingAnimations.isEmpty()) {
                // We only want to start running animations once all mPreProcessingAnimations have
                // been processed to ensure preprocessed animations start in sync.
                // NOTE: This means we might delay running animations that require preprocessing if
                // new animations that also require preprocessing are requested before the previous
                // ones have finished (see b/227449117).
                return;
            }
            // 主流程
            startPendingAnimationsLocked();
        }
        mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
    }
    private void startPendingAnimationsLocked() {
        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
            startAnimationLocked(mPendingAnimations.valueAt(i));
        }
        mPendingAnimations.clear();
    }

3.2 动画执行

下面就是动画本身了

scss 复制代码
# SurfaceAnimationRunner
    private void startAnimationLocked(RunningAnimation a) {
        
        final ValueAnimator anim = mAnimatorFactory.makeAnimator();

        // Animation length is already expected to be scaled.
        anim.overrideDurationScale(1.0f);
        // 动画时长
        anim.setDuration(a.mAnimSpec.getDuration());
        // 动画更新
        anim.addUpdateListener(animation -> {
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    final long duration = anim.getDuration();
                    long currentPlayTime = anim.getCurrentPlayTime();
                    if (currentPlayTime > duration) {
                        currentPlayTime = duration;
                    }
                    applyTransformation(a, mFrameTransaction, currentPlayTime);
                }
            }

            // Transaction will be applied in the commit phase.
            scheduleApplyTransaction();
        });
        // 动画开始结束监听
        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                synchronized (mCancelLock) {
                    if (!a.mCancelled) {
                        // TODO: change this back to use show instead of alpha when b/138459974 is
                        // fixed.
                        // 动画开始
                        mFrameTransaction.setAlpha(a.mLeash, 1);
                    }
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                synchronized (mLock) {
                    mRunningAnimations.remove(a.mLeash);
                    synchronized (mCancelLock) {
                        if (!a.mCancelled) {
                            // 动画结束
                            // Post on other thread that we can push final state without jank.
                            mAnimationThreadHandler.post(a.mFinishCallback);
                        }
                    }
                }
            }
        });
        a.mAnim = anim;
        mRunningAnimations.put(a.mLeash, a);
        // 动画开始
        anim.start();
        if (a.mAnimSpec.canSkipFirstFrame()) {
            // If we can skip the first frame, we start one frame later.
            anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
        }

        // Immediately start the animation by manually applying an animation frame. Otherwise, the
        // start time would only be set in the next frame, leading to a delay.
        // 注释的大概意思为:避免延迟,所以手动触发动画
        anim.doAnimationFrame(mChoreographer.getFrameTime());
    }

动画是咋执行的,问题不大,看看就行,无法就是加载之前的动画执行做执行。

3.2 动画结束

动画结束后会执行之前传递过来的结束回调,也就是 SurfaceRotationAnimationController::onAnimationEnd 下面就是一些收尾工作了。

java 复制代码
# SurfaceRotationAnimationController
        private void onAnimationEnd(@AnimationType int type, AnimationAdapter anim) {
            synchronized (mService.mGlobalLock) {
                if (isAnimating()) {
                    ProtoLog.v(WM_DEBUG_ORIENTATION,
                            "ScreenRotation still animating: type: %d\n"
                                    + "mDisplayAnimator: %s\n"
                                    + "mEnterBlackFrameAnimator: %s\n"
                                    + "mRotateScreenAnimator: %s\n"
                                    + "mScreenshotRotationAnimator: %s",
                            type,
                            mDisplayAnimator != null
                                    ? mDisplayAnimator.isAnimating() : null,
                            mEnterBlackFrameAnimator != null
                                    ? mEnterBlackFrameAnimator.isAnimating() : null,
                            mRotateScreenAnimator != null
                                    ? mRotateScreenAnimator.isAnimating() : null,
                            mScreenshotRotationAnimator != null
                                    ? mScreenshotRotationAnimator.isAnimating() : null
                    );
                    return;
                }
                //  动画结束日志
                ProtoLog.d(WM_DEBUG_ORIENTATION, "ScreenRotationAnimation onAnimationEnd");
                mEnterBlackFrameAnimator = null;
                mScreenshotRotationAnimator = null;
                mRotateScreenAnimator = null;
                mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION;
                if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
                    // It also invokes kill().
                    // 置空
                    mDisplayContent.setRotationAnimation(null);
                } else {
                    kill();
                }
                mService.updateRotation(false, false);
            }
        }

DisplayContent 的处理如下

csharp 复制代码
# DisplayContent
    public void setRotationAnimation(ScreenRotationAnimation screenRotationAnimation) {
        if (mScreenRotationAnimation != null) {
            
            mScreenRotationAnimation.kill();
        }
        mScreenRotationAnimation = screenRotationAnimation;
        ......
    }

4. 总结

整个调用链如下:

arduino 复制代码
RootWindowContainer::performSurfacePlacementNoTrace
   RootWindowContainer::applySurfaceChangesTransaction
   RootWindowContainer::checkAppTransitionReady
   WindowManagerService::stopFreezingDisplayLocked -- mOrientationChangeComplete为true有解锁
      WindowManagerService::doStopFreezingDisplayLocked  --  需要满足条件
        ScreenRotationAnimation::dismiss
            ScreenRotationAnimation::startAnimation
                ScreenRotationAnimation::deltaRotation  -- 计算动画旋转角度
                AnimationUtils.loadAnimation            -- 根据角度加载动画资源,赋值给mRotateExitAnimation,mRotateEnterAnimation
                    SurfaceRotationAnimationController::startScreenRotationAnimation --默认不是自定义动画
                    SurfaceRotationAnimationController::startDisplayRotation  -- 开始旋转动画
                        SurfaceAnimator.Animatable::init
                        SurfaceRotationAnimationController::createWindowAnimationSpec
                            WindowAnimationSpec::init -- 保存动画资源
                        SurfaceRotationAnimationController::startAnimation -- 开始动画流程
                            SurfaceRotationAnimationController::startAnimation
                                SurfaceAnimator::createAnimationLeash  -- 创建leash图层
                                LocalAnimationAdapter::startAnimation 
                                    SurfaceAnimationRunner::startAnimation
                                        RunningAnimation::init
                                        mPendingAnimations::put    -- 动画添加到集合
                                        Choreographer::postFrameCallback  --  下一帧 Vsync 开始动画
                                        -----Vsync来到后----
                                        SurfaceAnimationRunner::startAnimations
                                            SurfaceAnimationRunner::startPendingAnimationsLocked
                                                mPendingAnimations::valueAt  --  取出动画
                                                SurfaceAnimationRunner::startAnimationLocked  -- 动画流程
                                                    SurfaceRotationAnimationController::onAnimationEnd -- 动画结束
                                                mPendingAnimations::clear

这个流程中动画本身其实还好,主要在执行 WindowManagerService::doStopFreezingDisplayLocked 方法前的一些条件比较重要,比如 mOrientationChangeComplete 这个变量,和 ActivityRecord 的冻结和解冻。

相关推荐
长亭外的少年4 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿6 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神7 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛8 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法8 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter9 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快10 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl11 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江11 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-11 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记