StaringWindow的创建与移除 - finalxiaoxiao/study GitHub Wiki

StaringWindow的创建与移除

Table of Contents
  • 本文主要研究内容:

    • StaringWindow是什么

    • StartingWindow的框架

    • SplashScreen类型StartingWindow的创建和销毁

    • SnapShot类型StartingWindow的创建和销毁

    • TaskSnapshot的创建与销毁

  • 本文研究基于的版本:
    android-11.0.0_r3

在Activity真正显示之前,可能需要处理大量的耗时任务,像进程创建,资源加载,窗口绘制等等。所以在窗口的过渡动画完成之后,可能应用还没有完成页面的绘制,我们需要一个页面来等待真正的Activity显示。又或者说窗口的过渡动画使用什么素材呢?

StaringWindow的存在就是为了解决这样的问题,它所扮演的角色便是 应用启动 时的窗口代替者。

和Activity的窗口一样,StaringWindow也是由WindowState和Surface构成的。它使用的窗口类型是TYPE_APPLICATION_STARTING。在不同的场景,有不同类型的StaringWindow:

  • STARTING_WINDOW_TYPE_NONE
    不启动StaringWindow,常见于应用内的Activity切换。

  • STARTING_WINDOW_TYPE_SNAPSHOT
    快照启动窗口,显示的内容为最近一次的可见内容的快照。使用场景如task从后台到前台的切换,屏幕解锁。

  • STARTING_WINDOW_TYPE_SPLASH_SCREEN
    闪屏启动窗口,显示的内容是空白的窗口,背景和应用的主题有关。使用场景如应用的冷启动。

  • ActivityStack
    存放task的ActivityRecord的容器,也是处理ActivityRecord生命周期的主要参与者。starting window的启动流程起点就是ActivityStack.startActivityLocked()。

  • ActivityRecord
    系统进程中的Activity,也是窗口容器。Activity窗口和启动窗口都是它的child。因此 启动窗口的添加和移除都是由ActivityRecord负责的。

  • WindowState
    系统进程中的窗口,在窗口管理系统中是控制页面大小位置等属性的基本单元。starting window启动中会创建一个TYPE_APPLICATION_STARTING窗口类型的WindowState。

  • StartingData
    抽象了starting window的数据模型,负责构造 StartingSurface。

    • SplashScreenStartingData
      闪屏类型启动窗口的StartingData实现,封装了闪屏类型启动窗口所需要的数据如theme,icon,windowflags等等,这些数据来源于ActivityRecord。

    • SnapshotStartingData
      快照类型启动窗口的StartingData实现,持有了TaskSnapshot。

  • StartingSurface
    用于封装starting window的内容。使用remove()移除starting window。

    • SplashScreenSurface
      持有一个View,remove()使用WindowManager.removeView()。

    • TaskSnapshotSurface
      由于不使用View直接使用应用的截图缓存绘制窗口,它直接向WMS申请WindowState和布局,在Surface上绘制内容。remove()也是直接使用IWindowSession.remove()。这样一看它的职能和ViewRootImpl类似,是一个没有View的简易ViewRootImpl。

209103618 e86a32db 0c85 4754 a292 a2efb8759182
209103794 ec544160 617a 4258 be43 433cbbd5cacc
  1. StaringWindow的创建时机在Activity的启动时。在前面提到Activity的启动是需要一个空白页面或者截图页面过渡的。所以在系统进程收到Activity的启动请求时,根据不同的场景分配不同的启动窗口类型,绘制启动窗口。

  2. StaringWindow的移除时机在Activity的绘制完成之后,当Activity完成绘制之后,StartingWindow的使命也就结束了,所以将它移除。

209103850 59770777 a751 47b5 b20b a8d9118f6f91

从时序图中,我们可以看出StaringWindow的创建与移除主要通过StartingSurface的create和remove实现的。

在Launcher启动App场景下,StartingWindow的启动和移除入口是ActivityStack.startActivityLocked()和WindowManagerService.finishDrawing()。

Tip

在Recents启动App、屏幕解锁的场景下,StaringWindow的add与remove会走其他的通道, 但是内部实现是一致的。

当应用冷启动的时候,会添加SplashScreen类型的StartingWindow。让我们通过代码梳理SplashScreen类型的StaringWindow的添加与移除流程。

class ActivityStack extends Task {
    ...
    void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
        boolean newTask, boolean keepCurTransition, ActivityOptions options) {
        ...
        boolean doShow = true; // (1)
        ...
        if (r.mLaunchTaskBehind) {
        } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
        ...
        r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));
        }
        ...
    }
    ...
}
  1. doShow指是否执行ActivityRecord.showStartingWindow方法,在某些场景下会被置为false

final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
    ...
    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
        ...
        final boolean shown = addStartingWindow(packageName, theme,
                compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
                allowTaskSnapshot(),
                mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal()); // (1)
        if (shown) {
            mStartingWindowState = STARTING_WINDOW_SHOWN; // (2)
        }
        ...
    }
    boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
            CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
            IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
            boolean allowTaskSnapshot, boolean activityCreated) {
        ...
        final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
                allowTaskSnapshot, activityCreated, snapshot); // (3)
        if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
            ...
            return createSnapshot(snapshot);
        }
        ...
        if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
            return false;
        }
        mStartingData = new SplashScreenStartingData(mWmService, pkg,
                theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                getMergedOverrideConfiguration());
        scheduleAddStartingWindow(); // (4)
    }
    ...
}
  1. 可以发现ActivityRecord把theme,campatInfor, icon等等这些Activity相关的属性传入addStartingWindow中,这其实是为了让StartingWindow在显示上尽量和实际显示的Activity相似。

  2. StartingWindow的状态有三种,NOT_SHOWN→SHOWN→REMOVED。知道它的显示状态有什么作用呢?在Activity的启动过程中,如果发现有其他的ActivityRecord的mStartingWindowState还处于SHOWN,那就说明它的remove流程出现了问题,是个orphaned starting window孤儿启动窗口,那么我们就可以把它remove回收了。

  3. type,启动窗口类型,它决定了接下来的程序走向,snapshot类型的启动窗口将会在之后详细展开。

  4. 在保存SplashScreen的StartingData之后,异步添加启动窗口,这是为了不让启动窗口的绘制阻塞Activity的启动。

final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
    ...
    private int getStartingWindowType(boolean newTask, boolean taskSwitch,
            boolean processRunning, boolean allowTaskSnapshot,
            boolean activityCreated, ActivityManager.TaskSnapshot snapshot) {
        if (newTask || !processRunning || (taskSwitch && !activityCreated)) { // (1)
            return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
        } else if (taskSwitch && allowTaskSnapshot) { // (2)
            if (isSnapshotCompatible(snapshot)) {
                return STARTING_WINDOW_TYPE_SNAPSHOT;
            }
            if (!isActivityTypeHome()) {
                return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
            }
            return STARTING_WINDOW_TYPE_NONE;
        } else {
            return STARTING_WINDOW_TYPE_NONE;
        }
    }
    ...
}
  1. newTask、进程未启动、切换task且新创建Activity时,获得的type为splash screen,否则进入第二步。(启动模式使用newTask、冷启动、新启动一个Activity并且切换task都会使用splash screen)

  2. 切换task且允许snapshot时,尝试获取STARTING_WINDOW_TYPE_SNAPSHOT(isSnapshotCompatible是指Activity的屏幕方向和截图的屏幕方向是否一致,如果不一致那自然不能使用截图作为启动窗口)。若不满足条件进入第三步

  3. 不创建StartingWindow(这种场景常见于应用内的Activity切换)。

final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
    ...
    void scheduleAddStartingWindow() {
        if (!mWmService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Enqueueing ADD_STARTING");
            mWmService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
        }
    }
    private class AddStartingWindow implements Runnable {

        @Override
        public void run() {
            // Can be accessed without holding the global lock
            ...
            WindowManagerPolicy.StartingSurface surface = null;
            try {
                surface = startingData.createStartingSurface(ActivityRecord.this);
            } catch (Exception e) {
                Slog.w(TAG, "Exception when adding starting window", e);
            }
            ...
        }
    }
}
class SplashScreenStartingData extends StartingData {
    ...
    @Override
    StartingSurface createStartingSurface(ActivityRecord activity) {
        return mService.mPolicy.addSplashScreen(activity.token, mPkg, mTheme, mCompatInfo,
                mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
                mMergedOverrideConfiguration, activity.getDisplayContent().getDisplayId());
    }
    ...
}

把添加Splash Screen类型的启动窗口的任务交给了PhoneWindowManager

public class PhoneWindowManager implements WindowManagerPolicy {
...
    @Override
    public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
            int logo, int windowFlags, Configuration overrideConfig, int displayId) {
        ...
        WindowManager wm = null;
        View view = null;
        ...
        final PhoneWindow win = new PhoneWindow(context); // (1)
        win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
        ...
        win.setFlags(
                windowFlags|
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                windowFlags|
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); // (2)
        win.setDefaultIcon(icon);
        win.setDefaultLogo(logo);
        final WindowManager.LayoutParams params = win.getAttributes();
        params.token = appToken; // (3)
        params.packageName = packageName;
        params.windowAnimations = win.getWindowStyle().getResourceId(
                com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
        params.privateFlags |=
                WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
        params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;

        if (!compatInfo.supportsScreen()) {
            params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
        }

        params.setTitle("Splash Screen " + packageName);
        addSplashscreenContent(win, context);

        wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
        view = win.getDecorView();
        wm.addView(view, params); // (4)
        return view.getParent() != null ? new SplashScreenSurface(view, appToken) : null;
        ...
    }
...
}
  1. 我们get了除Activity、Dialog之外,PhoneWindow的又一个用法。作为Activity的代替者,StartingWindow的绘制和Activity的绘制十分的类似。

  2. StaringWindow只是暂时代替Activity显示,所以无需处理Input事件的,在这里设置Flag禁用touch和Focus。

  3. 和Activity使用同样的appToken,建立StaringWindow与ActivityRecord之间的桥梁。

  4. 使用addView添加启动窗口。ActivityRecord会保存StartingSurface对象,在Activity绘制完成时,通过这个StartingSurface移除启动窗口。

回顾之前提到的StaringWindow的添加与移除主流程,在Activity绘制完成之后系统会触发StartingWindow的移除机制。那么我们接着从WindowManagerService.finishDrawingWindow()开始分析。

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
    ...
    void finishDrawingWindow(Session session, IWindow client,
        @Nullable SurfaceControl.Transaction postDrawTransaction) {
        ...
        WindowState win = windowForClientLocked(session, client, false);
        if (win != null && win.finishDrawing(postDrawTransaction)) {
            ...
            mWindowPlacerLocked.requestTraversal();
        }
        ...
    }
    ...
}

WindowManagerService.finishDrawingWindow()是窗口绘制流程中的内容,这里就不详细展开。直接进入其中的WindowState.performShowLocked()方法中分析。

class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
        InsetsControlTarget {
    ...
    boolean performShowLocked() {
        ...
        if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
            if (mAttrs.type != TYPE_APPLICATION_STARTING) {
                mActivityRecord.onFirstWindowDrawn(this, mWinAnimator); // (1)
            }
            ...
        }
        ...
    }
    ...
}
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
    ...
    void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
        ...
        removeStartingWindow();
        ...
    }
    void removeStartingWindow() {
        ...
        final WindowManagerPolicy.StartingSurface surface;
        if (mStartingData != null) {
            surface = startingSurface;
            mStartingData = null;
            startingSurface = null;
            startingWindow = null;
            startingDisplayed = false;
            if (surface == null) {
                ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
                        "startingWindow was set but startingSurface==null, couldn't "
                                + "remove");
                return;
            }
        } // (2)
        ...
        mWmService.mAnimationHandler.post(() -> {
            try {
                surface.remove();
            } catch (Exception e) {
                Slog.w(TAG_WM, "Exception when removing starting window", e);
            }
        }); // (3)
    }
    ...
}
  1. 当窗口类型不是TYPE_APPLICATION_STARTING,这表明真正的Activity的窗口已经绘制完成了,正式进入StaringWindow的移除流程。

  2. 重置staringWindow的状态值,如果surface为空说明StartingWindow已经在其他的地方被移除了。

  3. surface的remove流程也就是窗口的remove流程,耗时较长需要异步执行。

class SplashScreenSurface implements StartingSurface {
    @Override
    public void remove() {
        ...
        final WindowManager wm = mView.getContext().getSystemService(WindowManager.class);
        wm.removeView(mView);
    }
}

SplashScreenSurface的remove和Activity的窗口移除流程类似,也是通过WindowManager.remove完成。其中流程属于窗口的销毁流程,主要内容就是回收窗口添加时创建的数据,如ViewRootImpl,WindowState等等,这里就不详细展开了。

当ActivityRecord.getStartingWindowType()获取到的类型为STARTING_WINDOW_TYPE_SNAPSHOT时,会添加SNAPSHOT类型的StartingWindow。之前已经分析了SplashScreen类型的启动窗口流程,所以我们接下来只需要对比分析SnapShot的它们之间的差异部分。

启动窗口的主流程都是一致的,只是窗口中填充的内容不一样,所以我们需要分析StartingData和StartingSurface的差异。

class SnapshotStartingData extends StartingData {
    ...
    @Override
    StartingSurface createStartingSurface(ActivityRecord activity) {
        return mService.mTaskSnapshotController.createStartingSurface(activity, mSnapshot);
    }
}

class TaskSnapshotController {
    ...
    StartingSurface createStartingSurface(ActivityRecord activity,
        TaskSnapshot snapshot) {
        return TaskSnapshotSurface.create(mService, activity, snapshot);
    }
}
class TaskSnapshotSurface implements StartingSurface {
    ...
    static TaskSnapshotSurface create(WindowManagerService service, ActivityRecord activity,
            TaskSnapshot snapshot) {

        final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        final Window window = new Window();
        final IWindowSession session = WindowManagerGlobal.getWindowSession();
        window.setSession(session);
        final SurfaceControl surfaceControl = new SurfaceControl();
        ...
        }
        try {
            final int res = session.addToDisplay(window, window.mSeq, layoutParams,
                    View.GONE, activity.getDisplayContent().getDisplayId(), tmpFrame, tmpRect,
                    tmpRect, tmpCutout, null, mTmpInsetsState, mTempControls); // (1)
            ...
        } catch (RemoteException e) {
            // Local call.
        }
        final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
                surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis,
                windowFlags, windowPrivateFlags, taskBounds, currentOrientation, insetsState); // (2)
        window.setOuter(snapshotSurface);
        try {
            session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1,
                    tmpFrame, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect,
                    tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState,
                    mTempControls, sTmpSurfaceSize, sTmpSurfaceControl); // (3)
        } catch (RemoteException e) {
            // Local call.
        }

        ...
        snapshotSurface.drawSnapshot(); // (4)
        return snapshotSurface;
    }

}
  1. 在准备好了LayoutParams,IWindow等等添加窗口所需要的数据,向WMS申请添加WindowState。

  2. 将绘制相关的参数保存到TaskSnapshotSurface,为之后绘制页面做准备。

  3. 向WMS申请布局,在这里完成了Surface的创建。

  4. 绘制SnapShot中的内容,主要步骤是将TaskSnapShot中的绘图buffer应用到Surface上。

class TaskSnapshotSurface implements StartingSurface {
    ...
    private void drawSnapshot() {
        ...
        drawSizeMatchSnapshot();
        ...
        reportDrawn();
        ...
    }
    ...
    private void drawSizeMatchSnapshot() {
        mSurface.attachAndQueueBufferWithColorSpace(mSnapshot.getSnapshot(),
                mSnapshot.getColorSpace()); // (1)
        mSurface.release();
    }
    ...
    private void reportDrawn() {
        try {
            mSession.finishDrawing(mWindow, null /* postDrawTransaction */); // (2)
        } catch (RemoteException e) {
            // Local call.
        }
    }
    ...
}
  1. 将之前截图的GraphicBuffer设置到Surface上

  2. 通知WMS绘制完成,WMS会将窗口的绘制状态更新并将Surface设置为可见。

同样只分析remove流程的差异部分

class TaskSnapshotSurface implements StartingSurface {
    ...
    @Override
    public void remove() {
        ...
        mSession.remove(mWindow); // (1)
        ...
    }
    ...
}
  1. 通知WMS移除WindowState和Surface。WindowManager.removeView()也会使用到IWindowSession.remove(),还会对ViewRootImpl进行回收。

SnapShot类型的StaringWindow的绘制流程中使用的TaskSnapshot是如何获取的呢?我们接下来分析它的创建与移除流程。

  • WindowManagerService
    窗口管理服务,TaskSnapshotController是它的成员之一。

  • TaskSnapshotController
    是TaskSnapShot对外的接口,管理TaskSnapshot的创建,获取与销毁。

  • TaskSnapshotLoader
    从磁盘中加载TaskSnapshot,TaskSnapShot不仅会缓存在内存里,还会写在磁盘中。TaskSnapshotLoader负责从磁盘中恢复应用快照。(示例场景:启动数个应用→重启系统→进入多任务。在这种场景下,系统重启之后,多任务的显示需要页面快照,系统进程此时没有TaskSnapShot的缓存,这个时候便会从磁盘中加载快照。)

  • TaskSnapshotPersister
    负责TaskSnapshot在磁盘中的创建,读取与销毁。

  • TaskSnapshotCache
    负责TaskSnapshot在内存中的创建,读取与销毁。当应用从前台退到后台时,TaskSnapShot就会被缓存在TaskSnapshotCache中。并且ActivityRecord与TaskId存在映射,TaskId与TaskSnapShot存在映射。这样一来,在应用从后台回到前台时,通过taskId便能获取TaskSnapShot;在应用退出时,能通过ActivityRecord获取TaskSnapShot,将TaskSnapShot移除。

    • TaskSnapshot
      应用的快照,存储着应用快照的数据,如Task的Id、Size、TopActivity的ComponentName和最关键的GraphicBuffer。TaskSnapshot实现了Parcelable,因为其他应用也需要应用快照例如Launcher的多任务。

image::https://user-images.githubusercontent.com/49864524/209103993-6fc2163e-980f-4ec2-aa01-b98e08e7e892.png

image::https://user-images.githubusercontent.com/49864524/209104009-397d0a80-78d3-4a49-a121-09518fd58890.png

如图是比较常见的TaskSnapShot的创建与移除入口。

  • 应用切换至后台。因为应用从后台切换到前台需要使用快照填充启动窗口,所以需要在它进入后台时保存快照。应用的切场会填充过渡动画,task的快照流程的起点被设计在过渡动画的起点。

  • Back键退出应用。使用back键退出应用时Activity会被销毁,因此再次启动应用时不属于热启动,不需要应用快照。但是多任务页面仍需要应用快照,所以只移除应用快照的缓存。

  • 从多任务退出应用。从多任务页面中移除应用时进程会被销毁,因此再次启动应用属于冷启动。此时没有任何场景需要再使用应用快照,所以将它的缓存和磁盘存储全部移除。

image::https://user-images.githubusercontent.com/49864524/209104056-b0b84d32-f433-4300-8756-f85a515f05bb.png

  • 当过渡动画开始执行时,过渡动画控制类AppTransitionController通知snapshot控制类TaskSnapshotController,过渡动画开始执行。那么TaskSnapshotController会将正在关闭的应用的快照存储到缓存和磁盘。

  • activity执行完destroy流程,将状态回传给ATMS。这一状态被TaskSnapshotController所关注,如果destroy的Activity与缓存中的snapshot有映射,那么这个snapshot的缓存会被移除。

  • IPC调用的removeTask总是会将task从多任务中移除,而TaskSnapshotController关心多任务中的应用状态。当task在多任务中被移除时,这个task所对应的snapshot缓存和持久化存储都会被移除。

TaskSnapShot的创建在过渡动画开始时执行,过渡动画的创建执行流程不在本文讨论范围之内,我们只需要知道过渡动画的大致流程为:准备过渡动画→执行过渡动画。

  • 当应用启动时和应用退到后台时,系统进程都会为应用准备过渡动画。

  • 执行过渡动画的时机位于WMS的布局流程中,只有在窗口完成绘制之后,才能执行过渡动画。

我们以WMS的布局流程为起点为入口,分析TaskSnapShot的创建流程。

class RootWindowContainer extends WindowContainer<DisplayContent>
        implements DisplayManager.DisplayListener {
    ...
    void performSurfacePlacementNoTrace() {
        ...
        checkAppTransitionReady(surfacePlacer); // (1)
        ...
    }
    private void checkAppTransitionReady(WindowSurfacePlacer surfacePlacer) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final DisplayContent curDisplay = mChildren.get(i);
            if (curDisplay.mAppTransition.isReady()) { // (2)
                curDisplay.mAppTransitionController.handleAppTransitionReady();
                ...
            }
        }
        ...
    }
    ...
}

public class AppTransitionController {
    void handleAppTransitionReady() {
        ...
        mService.mTaskSnapshotController.onTransitionStarting(mDisplayContent); // (3)
        ...
    }
}
  1. 在布局循环开始之时,先检查过渡动画是否准备好了。

  2. 启动应用时prepare过渡动画,应用完成resume时execute过渡动画,所以此时可以handle过渡动画。总的来说就是三步曲prepare→execute→handle

  3. 此时动画已经配置到窗口了,通知TaskSnapshotController过渡动画正在开始。

class TaskSnapshotController {
    void onTransitionStarting() {
        handleClosingApps(displayContent.mClosingApps); // (1)
    }
    private void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
        if (shouldDisableSnapshots()) {
            return;
        }
        getClosingTasks(closingApps, mTmpTasks); // (2)
        snapshotTasks(mTmpTasks); // (3)
        mSkipClosingAppSnapshotTasks.clear();
    }
    void getClosingTasks(ArraySet<ActivityRecord> closingApps, ArraySet<Task> outClosingTasks) {
        outClosingTasks.clear();
        for (int i = closingApps.size() - 1; i >= 0; i--) {
            final ActivityRecord activity = closingApps.valueAt(i);
            final Task task = activity.getTask();

            if (task != null && !task.isVisible() && !mSkipClosingAppSnapshotTasks.contains(task)) {
                outClosingTasks.add(task);
            }
        }
    }
}
  1. ActivityRecord设置可见性时,如果ActivityRecord即将不可见,那么会将ActivityRecord添加到displayContent.mClosingApps。

  2. 并不是所有的closingApps都需要进行截图。因为截图是针对task的,只有即将不可见的task才需要截图。例如在应用内切换activity时,前一个Activity会被添加到closingApps,但是task还是可见的,所以并不会对前一个Activity进行截图。

  3. 对过滤之后的closingApps进行缓存和持久化存储操作。

class TaskSnapshotController {
    void snapshotTasks(ArraySet<Task> tasks) {
        snapshotTasks(tasks, false /* allowSnapshotHome */);
    }
    private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
        for (int i = tasks.size() - 1; i >= 0; i--) {
            final Task task = tasks.valueAt(i);
            final TaskSnapshot snapshot;
            final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
            if (snapshotHome) {
                snapshot = snapshotTask(task); // (1)
            } else {
                switch (getSnapshotMode(task)) {
                    case SNAPSHOT_MODE_NONE: // (2)
                        continue;
                    case SNAPSHOT_MODE_APP_THEME:
                        snapshot = drawAppThemeSnapshot(task); // (3)
                        break;
                    case SNAPSHOT_MODE_REAL:
                        snapshot = snapshotTask(task); // (4)
                        break;
                    default:
                        snapshot = null;
                        break;
                }
            }
            mCache.putSnapshot(task, snapshot); // (5)
            if (!snapshotHome) {
                mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); // (6)
                task.onSnapshotChanged(snapshot);
            }
        }
    }
    int getSnapshotMode(Task task) {
        final ActivityRecord topChild = task.getTopMostActivity();
        if (!task.isActivityTypeStandardOrUndefined() && !task.isActivityTypeAssistant()) {
            return SNAPSHOT_MODE_NONE;
        } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
            return SNAPSHOT_MODE_APP_THEME;
        } else {
            return SNAPSHOT_MODE_REAL;
        }
    }
}
  1. allowSnapshotHome在本流程中为false,所以不会进入该分支。如果锁屏被设置了密码,那么在息屏流程中就会设置allowSnapshotHome为true,会进入该分支。

  2. 如果ActivityType不是ACTIVITY_TYPE_UNDEFINED、ACTIVITY_TYPE_STANDARD、ACTIVITY_TYPE_ASSISTANT中的一种,那么是不会为其创建snapshot。很显然ACTIVITY_TYPE_HOME是被排除在外的。

  3. SNAPSHOT_MODE_APP_THEME模式表示不希望使用窗口的内容构建snapshot,只使用App的theme。例如被标记了WindowManager.LayoutParams.FLAG_SECURE的安全窗口;像PipMenuActivity设置了ATMS.setDisablePreviewScreenshots()。

  4. SNAPSHOT_MODE_REAL模式使用task的页面的内容构建snapshot。

  5. 缓存snapshot

  6. 如果不是home类型的task,持久化存储snapshot。

Note

解锁流程和点击home键流程,都不会为HomeActivity添加启动窗口,那为什么还要在息屏流程中设置allowSnapshotHome为true呢?

class TaskSnapshotController {
    ...
    private TaskSnapshot drawAppThemeSnapshot(Task task) {
        ...
        final int color = ColorUtils.setAlphaComponent(
                task.getTaskDescription().getBackgroundColor(), 255);
        ...
        final RenderNode node = RenderNode.create("TaskSnapshotController", null);
        ...
        final RecordingCanvas c = node.start(width, height);
        c.drawColor(color); // (1)
        ...
        final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
        ...

        return new TaskSnapshot(..., hwBitmap.createGraphicBufferHandle(), ...); // (2)
    }
    ...
}
  1. 设置页面颜色,color值从task背景色获取,alpha值为255不透明。

  2. 从Bitmap获取GraphicBuffer,作为TaskSnapShot的内容,所以drawAppThemeSnapshot()就是使用task背景色构建snapshot。

class TaskSnapshotController {
    ...
    TaskSnapshot snapshotTask(Task task) {
        return snapshotTask(task, PixelFormat.UNKNOWN);
    }
    TaskSnapshot snapshotTask(Task task, int pixelFormat) {
        TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
        ...
        final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
                createTaskSnapshot(task, builder); // (1)
        builder.setSnapshot(screenshotBuffer.getGraphicBuffer());
        ...
        return builder.build();
    }
    SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
            TaskSnapshot.Builder builder) {
        Point taskSize = new Point();
        final SurfaceControl.ScreenshotGraphicBuffer taskSnapshot = createTaskSnapshot(task,
                mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize);
        builder.setTaskSize(taskSize);
        return taskSnapshot;
    }
    @Nullable
    SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
            float scaleFraction, int pixelFormat, Point outTaskSize) {
        ...
        final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
                SurfaceControl.captureLayersExcluding(
                        task.getSurfaceControl(), mTmpRect, scaleFraction,
                        pixelFormat, excludeLayers); // (2)
        ...
        return screenshotBuffer;
    }
    ...
}
  1. 构建TaskSnapShot的核心还是在于GraphicBuffer的获取,继续深入探索。

  2. 使用Task的SurfaceControl获取GraphicBuffer,这就解释的通了,窗口的buffer本就是SurfaceControl提供的。

class TaskSnapshotCache {
    ...
    void putSnapshot(Task task, TaskSnapshot snapshot) {
        final CacheEntry entry = mRunningCache.get(task.mTaskId);
        if (entry != null) {
            mAppTaskMap.remove(entry.topApp); // (1)
        }
        final ActivityRecord top = task.getTopMostActivity();
        mAppTaskMap.put(top, task.mTaskId); // (2)
        mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top)); // (3)
    }
    ...
}
  1. 更新操作,为了将新的ActivityRecord和task绑定在一起。

  2. ActivityRecord和taskId建立映射关系,为什么不直接使用RunningCache,非要整两个map呢?这是为了监听通过ActivityRecord的remove来更新RunningCache。

  3. taskId和CacheEntry建立映射关系,为什么需要使用CacheEntry呢?这是因为需要通过taskId找到ActivityRecord,这样TaskId和ActivityRecord完成了双向绑定。在ActivityRecord被移除时,可以顺着两个map移除snapshot;在添加新的snapshot时,将旧的ActivityRecord移除,当然CacheEntry会直接被新的覆盖。

Android的持久化存储基本都是使用Persister线程+Item队列的模式。

  • 考虑到耗时和多线程的原因,创建一个线程用于处理各个场景创建的文件操作任务

  • 把文件操作任务抽象成一个Item,这里是WriteQueueItem。然后派生不同的Item描述不同类型的操作。

class TaskSnapshotPersister {
    ...
    void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
        synchronized (mLock) {
            mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
            sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
        }
    }

    ...
    private class StoreWriteQueueItem extends WriteQueueItem {
        ...
        @Override
        void write() { // (1)
            if (!createDirectory(mUserId)) {
                Slog.e(TAG, "Unable to create snapshot directory for user dir="
                        + getDirectory(mUserId));
            }
            boolean failed = false;
            if (!writeProto()) {
                failed = true;
            }
            if (!writeBuffer()) {
                failed = true;
            }
            if (failed) {
                deleteSnapshot(mTaskId, mUserId);
            }
        }
        ...
    }
    ...
    @GuardedBy("mLock")
    private void sendToQueueLocked(WriteQueueItem item) {
        mWriteQueue.offer(item); // (2)
        ...
        mLock.notifyAll(); // (3)
        ...
    }
    ...
    private Thread mPersister = new Thread("TaskSnapshotPersister") {
        public void run() {
            android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            while (true) {
                WriteQueueItem next;
                synchronized (mLock) {
                    ...
                    next = mWriteQueue.poll();
                    ...
                }
                if (next != null) {
                    ...
                    next.write(); // (4)
                    ...
                }
                synchronized (mLock) {
                    final boolean writeQueueEmpty = mWriteQueue.isEmpty();
                    if (!writeQueueEmpty && !mPaused) {
                        continue;
                    }
                    ...
                    mLock.wait(); // (5)
                    ...
                }
            }
        }
    };
}
  1. StoreWriteQueueItem描述了存储磁盘的任务,在重写的write方法中,会创建文件存储snapshot的task信息,buffer信息。

  2. 入队操作

  3. 唤醒Persist线程

  4. 取出Item任务后执行

  5. 执行完队列的所有任务进入等待状态,等待另一个线程来唤醒

在什么时候我们需要移除TaskSnapShot呢?那自然是task被销毁的时候,我们接下来分析以下两种场景下,TaskSnapShot是如何被移除的:

  • 从Back键退出应用

  • 从多任务移除应用

一般来讲,Activity对back事件的处理是移除Activity。TaskSnapShotController对Activity的移除比较感兴趣,所以在Activity被移除时TaskSnapShotController会尝试移除TaskSnapShot的缓存。

public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
    ...
    @Override
    public final void activityDestroyed(IBinder token) {
        ...
        final ActivityRecord activity = ActivityRecord.forTokenLocked(token);
        if (activity != null) {
            activity.destroyed("activityDestroyed");
        }
        ...
    }
    ...
}

final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
    ...
    void destroyed(String reason) {
        ...
        removeFromHistory(reason);
        ...
    }
    void removeFromHistory(String reason) {
        ...
        removeAppTokenFromDisplay();
        ...
    }
    private void removeAppTokenFromDisplay() {
        ...
        dc.removeAppToken(appToken.asBinder());
        ...
    }
}

class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>
        implements WindowManagerPolicy.DisplayContentInfo {
    ...
    void removeAppToken(IBinder binder) {
        ...
        activity.onRemovedFromDisplay();
        ...
    }
    ...
}

final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
    ...
    void onRemovedFromDisplay() {
        ...
        mWmService.mTaskSnapshotController.onAppRemoved(this);
        ...
    }
}
class TaskSnapshotController {
    ...
    void onAppRemoved(ActivityRecord activity) {
        mCache.onAppRemoved(activity);
    }
    ...
}

class TaskSnapshotCache {
    ...
    void onAppRemoved(ActivityRecord activity) {
        final Integer taskId = mAppTaskMap.get(activity); // (1)
        if (taskId != null) {
            removeRunningEntry(taskId); // (2)
        }
    }
    void removeRunningEntry(int taskId) {
        final CacheEntry entry = mRunningCache.get(taskId);
        if (entry != null) {
            mAppTaskMap.remove(entry.topApp);
            mRunningCache.remove(taskId);
        }
    }
    ...
}
  1. TaskSnapShot的创建流程中,最后一次可见的ActivityRecord与taskId建立映射,所以当且仅当之前建立映射的ActivityRecord被移除时,才会移除缓存。

  2. 断开ActivityRecord、taskId与TaskSnapShot之间的映射关系。

从多任务移除应用往往意味着这个task被移除了。即也不需要从热启动恢复这个task,不需要从多任务恢复这个task,那么TaskSnapShot的缓存和持久存储都需要被移除。

public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
    ...
    @Override
    public boolean removeTask(int taskId) {
        ...
        return mStackSupervisor.removeTaskById(taskId, true, REMOVE_FROM_RECENTS,
                "remove-task");
        ...
    }
    ...
}

public class ActivityStackSupervisor implements RecentTasks.Callbacks {
    ...
    boolean removeTaskById(int taskId, boolean killProcess, boolean removeFromRecents,
            String reason) {
        ...
        removeTask(task, killProcess, removeFromRecents, reason);
        ...
    }
    void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
        ...
        cleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);
        ...
    }
    void cleanUpRemovedTaskLocked(Task task, boolean killProcess, boolean removeFromRecents) {
        ...
        if (removeFromRecents) {
            mRecentTasks.remove(task);
        }
        ...
    }
    ...
}

class RecentTasks {
    ...
    void remove(Task task) {
        mTasks.remove(task);
        notifyTaskRemoved(task, false /* wasTrimmed */, false /* killProcess */);
    }
    private void notifyTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess) {
        for (int i = 0; i < mCallbacks.size(); i++) {
            mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed, killProcess);
        }
        mTaskNotificationController.notifyTaskListUpdated();
    }
    ...
}

public class ActivityStackSupervisor implements RecentTasks.Callbacks {
    ...
    @Override
    public void onRecentTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess) {
        ...
        task.removedFromRecents();
    }
    ...
}

class Task extends WindowContainer<WindowContainer> {
    ...
    void removedFromRecents() {
        ...
        mAtmService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents(
                mTaskId, mUserId);
    }
    ...
}
class TaskSnapshotController {
    ...
    void notifyTaskRemovedFromRecents(int taskId, int userId) {
        mCache.onTaskRemoved(taskId); // (1)
        mPersister.onTaskRemovedFromRecents(taskId, userId);
    }
    ...
}

class TaskSnapshotCache {
    void onTaskRemoved(int taskId) {
        removeRunningEntry(taskId); // (2)
    }
    void removeRunningEntry(int taskId) {
        final CacheEntry entry = mRunningCache.get(taskId);
        if (entry != null) {
            mAppTaskMap.remove(entry.topApp);
            mRunningCache.remove(taskId);
        }
    }
}
  1. 当task在最近任务被移除时,移除缓存

  2. 移除两个map中的映射关系。

class TaskSnapshotPersister {
    ...
    void onTaskRemovedFromRecents(int taskId, int userId) {
        synchronized (mLock) {
            ...
            sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId)); // (1)
        }
    }
    private class DeleteWriteQueueItem extends WriteQueueItem {
        private final int mTaskId;
        private final int mUserId;

        DeleteWriteQueueItem(int taskId, int userId) {
            mTaskId = taskId;
            mUserId = userId;
        }

        @Override
        void write() {
            deleteSnapshot(mTaskId, mUserId); // (2)
        }
    }
    private void deleteSnapshot(int taskId, int userId) {
        final File protoFile = getProtoFile(taskId, userId);
        final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
        protoFile.delete();
        if (bitmapLowResFile.exists()) {
            bitmapLowResFile.delete();
        }
        final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
        if (bitmapFile.exists()) {
            bitmapFile.delete();
        }
    }
    ...
}
  1. 在之前的TaskSnapShot的持久化存储流程中有介绍Item队列会被Persist线程按序处理

  2. 通过taskId和userId找到TaskSnapShot文件存储的位置,并将其删除。

本文分析的内容主要为以下几点:

  • StartingWindow的框架:StartingWindow的类型、组成、创建和移除时机。

  • SplashScreen和SnapShot类型的启动窗口:

    • StartingWindow类型的获取

    • 分别使用PhoneWindow和GraphicBuffer绘制启动窗口

    • 移除StartingWindow的时机

  • TaskSnapShot的缓存和持久化存储的管理机制

    • 创建TaskSnapShot的时机

    • 缓存中ActivityRecord和TaskSnapShot的映射关系

    • 持久化存储使用的线程+Item机制

    • 移除缓存和持久化存储的时机

还需要了解的其他内容:

  • 过渡动画准备和执行流程

  • StartingWindow对Home Activity的特殊处理

⚠️ **GitHub.com Fallback** ⚠️