SplashScreen的添加与移除分析

1. StartWindow 介绍

写过 APP 同学都知道稍微有规模的应用启动的第一个都是 一个 SplashActivity ,也就是常说的"闪屏页",而不是会是有复杂业务的 "MainActivity" 。

这个 SplashActivity 的内容一般也很简单,要么是一个图片,或者是一个 logo,有的就是简短的一些文案。比如微信启动的时候会先显示一个"月球"的图片。 为什么要先显示这个 SplashActivity ?

因为包含业务的 MainActivity 要正常显示需要做一些初始化工作,比如会依赖很多数据,而这些数据的加载需要时间,大部分情况是需要等网络请求返回的数据填充页面,而这个时间是不确定的,那么在数据返回之前的页面可能会很杂乱,不美观。如果用户打开一个应用,几秒内看到的一直是一个这样的一个界面,是很影响用户体验的。

为了提升用户体验,就会先写一个 SplashActivity 在这里做一些初始化操作,一切准备就绪后再进入 MainActivity 。

这是 App 端的处理,再回到 WMS 这个角度,冷启动一个应用时,应用的第一个窗口从添加到绘制完成是需要时间的,为了提升这个过程的用户体验, google 的设计也是先显示一个 StartWindow 也就是本次要介绍的 Splash Screen 窗口。

那么为了和后续看到的代码的统一,本篇后续出现的 StartWindow 和 Splash Screen 都是指同一个窗口。

基于这些分析,那么对于 StartWindow 的分析,我认为需要搞懂以下4点:

  • 存在的意义
  • 添加的时机
  • 移除的时机
  • 窗口的内容
    第一点其实已经知道了,至于 StartWindow 添加和删除的时机,后面会从代码中看,现在先抛开代码冲业务角度分析,既然是为了过渡,那么添加的时机肯定是在应用窗口添加前,移除的时机必然是在真正的应用窗口显示之前
    至于 StartWindow 的内容,默认就是个应用 logo , 但是也支持一些自定义效果后续会介绍。

先通过以下命令打开 StartWindow 对应 ProtoLog

cpp 复制代码
    adb shell wm logging enable-text WM_SHELL_STARTING_WINDOW  WM_DEBUG_STARTING_WINDOW

然后在桌面点击电话图标启动应用在抓取的日志中可以看到以下log:

cpp 复制代码
    // ActivityRecord 的挂载 (需要打开 WM_DEBUG_ADD_REMOVE)
	WindowManager: Adding activity ActivityRecord{735a13f u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t32} to task Task{e1c6d6a #32 type=standard ......
	
    // 创建闪屏页需要的数据类
    WindowManager: Creating SplashScreenStartingData

    // 打印AddStartingWindow实例和这次用到的 StartingData,其实就是上面创建的 SplashScreenStartingData
    WindowManager: Add starting com.android.server.wm.ActivityRecord$AddStartingWindow@b971d3: startingData=com.android.server.wm.SplashScreenStartingData@bd5f110
   
    // 获取当前 StartWindow 的类型
    ShellStartingWindow: preferredStartingWindowType newTask=true, taskSwitch=true, processRunning=false, allowTaskSnapshot=true, activityCreated=false, isSolidColorSplashScreen=false, legacySplashScreen=false, activityDrawn=false, topIsHome=false

    // 为哪个Activity 添加StartingWindow
    ShellStartingWindow: addSplashScreen for package: com.google.android.dialer with theme: 7f160232 for task: 33, suggestType: 1

    // 打印几个窗口参数
	ShellStartingWindow: getWindowAttrs: window attributes color: 0, replace icon: false
	ShellStartingWindow: processAdaptiveIcon: FgMainColor=ff166cfe, BgMainColor=fff8f8f8, IsBgComplex=false, FromCache=true, ThemeColor=ffffffff
	ShellStartingWindow: isRgbSimilarInHsv a:ffffffff, b:fff8f8f8, contrast ratio:1.062016
	ShellStartingWindow: processAdaptiveIcon: choose fg icon

    // StartWindow的窗口挂载到窗口树
    WindowManager: addWindow: ActivityRecord{735a13f u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t33} startingWindow=Window{b971d3 u0 Splash Screen com.google.android.dialer}
    // 开始要执行移除 StartingWindow 的逻辑了,并打印堆栈
    WindowManager: Schedule remove starting ActivityRecord{735a13f u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t33} startingWindow=null startingView=null Callers=com.android.server.wm.ActivityRecord.removeStartingWindow:2715 com.android.server.wm.ActivityRecord.onFirstWindowDrawn:6456 com.android.server.wm.WindowState.performShowLocked:4710 com.android.server.wm.WindowStateAnimator.commitFinishDrawingLocked:291 com.android.server.wm.DisplayContent.lambda$new$8$com-android-server-wm-DisplayContent:998 

    // 可以开始移除 StartWindow 的 Surface 了
    Line 5389: 09-29 19:14:21.373 23407 23497 V ShellStartingWindow: Task start finish, remove starting surface for task: 28

    // 移除 splash screen 
    Line 5390: 09-29 19:14:21.373 23407 23497 V ShellStartingWindow: Removing splash screen window for task: 28
    
    // 开始移除 StartWindow 动画
    Line 5391: 09-29 19:14:21.373 23407 23497 V ShellStartingWindow: applyExitAnimation delayed: 0

可以看到log覆盖了从 add 到 remove 整个周期,那么只需要把日志的调用栈全部联系起来,那么就可以分析到 startWindow 完整的流程了。

StartWindow 的整个流程其实只是是冷启动 Activity 启动流程的中间一小块,Activity 启动流程流程之前已经分析过了,现在的主题是 StartingWindow ,所以还需要再简单的过一遍启动流程 StartingWindow 的处理。

上面日志里 ShellStartingWindow 打印的进程 ID 是 systemui 进程,这是因为 StartingWindow 的内容其实是在 systemui 进程处理的。

2. StartWindow 的添加流程

2.1 system_service进程前期处理

addStartingWindow 方法定义在 TaskOrganizerController 下,加上异常后获取到system_service进程打印了这么一个堆栈:

cpp 复制代码
09-29 19:30:11.190 30184 32263 E biubiubiu: TaskOrganizerController  addStartingWindow: ActivityRecord{e184810 u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t8}
09-29 19:30:11.190 30184 32263 E biubiubiu: java.lang.Exception
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.TaskOrganizerController.addStartingWindow(TaskOrganizerController.java:495)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.StartingSurfaceController.createSplashScreenStartingSurface(StartingSurfaceController.java:85)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.SplashScreenStartingData.createStartingSurface(SplashScreenStartingData.java:36)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityRecord$AddStartingWindow.run(ActivityRecord.java:2436)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityRecord.scheduleAddStartingWindow(ActivityRecord.java:2403)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityRecord.addStartingWindow(ActivityRecord.java:2382)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityRecord.showStartingWindow(ActivityRecord.java:7039)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityRecord.showStartingWindow(ActivityRecord.java:6999)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.StartingSurfaceController.showStartingWindow(StartingSurfaceController.java:197)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.Task.startActivityLocked(Task.java:5233)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityStarter.startActivityInner(ActivityStarter.java:1912)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityStarter.startActivityUnchecked(ActivityStarter.java:1668)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityStarter.executeRequest(ActivityStarter.java:1223)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityStarter.execute(ActivityStarter.java:709)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityTaskManagerService.startActivityAsUser(ActivityTaskManagerService.java:1250)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityTaskManagerService.startActivityAsUser(ActivityTaskManagerService.java:1213)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityTaskManagerService.startActivity(ActivityTaskManagerService.java:1188)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at android.app.IActivityTaskManager$Stub.onTransact(IActivityTaskManager.java:893)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at com.android.server.wm.ActivityTaskManagerService.onTransact(ActivityTaskManagerService.java:5244)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at android.os.Binder.execTransactInternal(Binder.java:1280)
09-29 19:30:11.190 30184 32263 E biubiubiu: 	at android.os.Binder.execTransact(Binder.java:1244)

整理后如下:

cpp 复制代码
    ActivityTaskManagerService::startActivity
        ActivityTaskManagerService::startActivityAsUser
            ActivityTaskManagerService::startActivityAsUser
                ActivityStarter::execute
                    ActivityStarter::executeRequest   -- 构建 ActivityRecord 
                        ActivityStarter::startActivityUnchecked
                            ActivityStarter::startActivityInner        -- 关键函数startActivityInner
                                ActivityStarter::getOrCreateRootTask   -- 创建或者拿到Task
                                ActivityStarter::setNewTask            -- 将task与activityRecord 绑定
    当前关注                     Task::startActivityLocked              -- **本文分析的StartWindow流程**
                                    StartingSurfaceController::showStartingWindow
                                        ActivityRecord::showStartingWindow
                                            ActivityRecord::showStartingWindow
                                                ActivityRecord::showStartingWindow
                                                    ActivityRecord::scheduleAddStartingWindow
                                                        ActivityRecord.AddStartingWindow::run
                                                            SplashScreenStartingData::createStartingSurface
                                                                StartingSurfaceController::createSplashScreenStartingSurface
                                                                    TaskOrganizerController::addStartingWindow   -- 开始跨进程
                                RootWindowContainer::resumeFocusedTasksTopActivities     --显示Activity(触发进程创建)

在 Activity 启动流程调用链中关注的 StartWindow 相关流程流程发现在 ActivityStarter::startActivityInner 就触发了,那就是和 Task 创建的同级调用顺序了。另外还可以确定StartWindow 的逻辑处理是在 Task 动画创建之前的。

那就从 ActivityStarter::startActivityInner 这个方法开始看。

cpp 复制代码
# ActivityStarter

    int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord......) {
            ......
            if (mTargetRootTask == null) {
                // 创建Task
                mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask,
                        mOptions);
            }
            ......
            将task与activityRecord 绑定
            setNewTask(taskToAffiliate);
            ......
            // 重点* startWindow流程
            mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch,
                mOptions, sourceRecord);
            ......
        }

这里可以看到做了三件事:

  • 创建 Task
  • 把 Activity 挂载到 Task 下面
  • 添加 StartWindow
    前面2个处理之前的文章已经分析了,当前关注第三点
cpp 复制代码
# Task
    void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask,
            boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) {
                ......
                if (r.mLaunchTaskBehind) {
                    ......
                } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
                    ......
                    Task baseTask = r.getTask();
                    ......

                    final ActivityRecord prev = baseTask.getActivity(
                            a -> a.mStartingData != null && a.showToCurrentUser());
                    mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask,
                            isTaskSwitch, sourceRecord);
                }
                ......
        }

当前场景这个 Task 就是"电话"应用的刚创建的 Task 。

"r.mLaunchTaskBehind"这条件是启动应用的时候用户看不见直接呈现在最近任务列表里,这种场景很少见,不用管,主要看下面的条件。SHOW_APP_STARTING_PREVIEW是个常量默认为true,可以设置为false表示不显示startWindow,doShow这个变量默认为true。那么主要看最后的那段逻辑即可。

cpp 复制代码
# StartingSurfaceController

    void showStartingWindow(ActivityRecord target, ActivityRecord prev,
            boolean newTask, boolean isTaskSwitch, ActivityRecord source) {
        if (mDeferringAddStartingWindow) {
            addDeferringRecord(target, prev, newTask, isTaskSwitch, source);
        } else {
            // 不延迟,正常走这
            target.showStartingWindow(prev, newTask, isTaskSwitch, true /* startActivity */,
                    source);
        }
    }

mDeferringAddStartingWindow 可以先不管,根据代码的注释是启动多个活动时推迟添加启动窗口时为 true 。

cpp 复制代码
# ActivityRecord

    // 调用的是这个,最后一个参数传递null继续调用
    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
            boolean startActivity, ActivityRecord sourceRecord) {
        showStartingWindow(prev, newTask, taskSwitch, isProcessRunning(), startActivity,
                sourceRecord, null /* candidateOptions */);
    }
    
    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
            boolean processRunning, boolean startActivity, ActivityRecord sourceRecord,
            ActivityOptions candidateOptions) {
            ......
            final boolean scheduled = addStartingWindow(packageName, resolvedTheme,
                prev, newTask || newSingleActivity, taskSwitch, processRunning,
                allowTaskSnapshot(), activityCreated, mSplashScreenStyleSolidColor, allDrawn);
            ......
        }
    // 调用这个
    @VisibleForTesting
    boolean addStartingWindow(String pkg, int resolvedTheme, ActivityRecord from, boolean newTask,
            boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot,
            boolean activityCreated, boolean isSimple,
            boolean activityAllDrawn) {
            ......
            // 注意* 在创建 SplashScreenStartingData 并输出日志
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");
            mStartingData = new SplashScreenStartingData(mWmService, resolvedTheme, typeParameter);
            // * 执行下一步
            scheduleAddStartingWindow();
            return true;
        }

        void scheduleAddStartingWindow() {
            // 就是执行其 run 方法
            mAddStartingWindow.run();
        }

    private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();

scheduleAddStartingWindow 方法内部是执行了一个Runnable

cpp 复制代码
# ActivityRecord.AddStartingWindow
    private class AddStartingWindow implements Runnable {

        @Override
        public void run() {
            // Can be accessed without holding the global lock
            final StartingData startingData;
            synchronized (mWmService.mGlobalLock) {
                    ......
                // mStartingData前面看到是SplashScreenStartingData类型
                startingData = mStartingData;
                ......
            }
            // 打印log
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s",
                        this, startingData);
            // 定义 StartWindow的surface,注意这里是自己定义的StartingSurface,不等于图层的Surface
            StartingSurfaceController.StartingSurface surface = null;
            try {
                    // 创建
                    surface = startingData.createStartingSurface(ActivityRecord.this);
                } catch (Exception e) {
                    Slog.w(TAG, "Exception when adding starting window", e);
            }
                ......
            }
    }

这里创建的 surface 是 StartWindow 模块自己的定义的类。

cpp 复制代码
# SplashScreenStartingData

    @Override
    StartingSurface createStartingSurface(ActivityRecord activity) {
        return mService.mStartingSurfaceController.createSplashScreenStartingSurface(
                activity, mTheme);
    }

# StartingSurfaceController
        StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, int theme) {

            synchronized (mService.mGlobalLock) {
                final Task task = activity.getTask();
                if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(
                        task, activity, theme, null /* taskSnapshot */)) {
                    // 创建返回StartingSurface,但是这个并不是真正的Surface,上面的addStartingWindow内部才会真正的触发
                    return new StartingSurface(task);
                }
            }
        return null;
    }

这块主要还是看 TaskOrganizerController::addStartingWindow 的流程,当前这个方法只要记住 StartingSurface 内部有个目标应用的 Task 就好了。

cpp 复制代码
# TaskOrganizerController

    private final LinkedList<ITaskOrganizer> mTaskOrganizers = new LinkedList<>();

    boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
            TaskSnapshot taskSnapshot) {
        // 拿到Task
        final Task rootTask = task.getRootTask();
        if (rootTask == null || activity.mStartingData == null) {
            return false;
        }
        // * 拿到对应的TaskOrganizer
        final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
        if (lastOrganizer == null) {
            return false;
        }
        final StartingWindowInfo info = task.getStartingWindowInfo(activity);
        if (launchTheme != 0) {
            // 注意这里传递过来了主题
            info.splashScreenThemeResId = launchTheme;
        }
        // 当前逻辑传递过来的taskSnapshot为null
        info.taskSnapshot = taskSnapshot;
        // make this happen prior than prepare surface
        try {
            // 重点* 跨进程触发addStartingWindow
            //这里两个重要参数info是StartingWindowInfo,token是activityRecord的token,其继承winwToken
            lastOrganizer.addStartingWindow(info, activity.token);
        } catch (RemoteException e) {
            Slog.e(TAG, "Exception sending onTaskStart callback", e);
            return false;
        }
        return true;
    }

直到这里都是在system_server进程,但是这里通过ITaskOrganizer调用后面的逻辑,后续的逻辑就是另一个进程了,目前需要这个lastOrganizer这个值到底是哪来的。这个lastOrganizer实际上就是SystemUI进程和system_service进程通信的binder类。这块的介绍在【ITaskOrganizer相关逻辑】。目前知道后续的处理进入SystemUI进程在TaskOrganizer下。

2.2 SystemUI进程创建 StartWindow

SystemUI进程这块的调用链整理如下:

cpp 复制代码
ITaskOrganizer::addStartingWindow
    ShellTaskOrganizer::addStartingWindow
        StartingWindowController::addStartingWindow
            StartingWindowTypeAlgorithm::getSuggestedWindowType    -- 获取类型
            StartingSurfaceDrawer::addSplashScreenStartingWindow   -- 核心方法
                SplashscreenContentDrawer::createContentView       -- 构建StartWindow的参数
                    SplashscreenContentDrawer::makeSplashScreenContentView
                        SplashscreenContentDrawer::getWindowAttrs  -- 获取应用配置的StartWindow的属性
                        StartingWindowViewBuilder::build           -- 构建SplashScreenView
                    SplashScreenViewSupplier::setView              -- 保存SplashScreenView
                StartingSurfaceDrawer::addWindow                   -- addWindow流程

继续看代码:

cpp 复制代码
# TaskOrganizer
    private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() {
        @Override
        public void addStartingWindow(StartingWindowInfo windowInfo,
                IBinder appToken) {
            // system_service跨进程是通过mInterface的对象调用到这
            mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo, appToken));
        }
        ......
    }

// 具体的处理在这
# ShellTaskOrganizer
    private StartingWindowController mStartingWindow;

    @Override
    public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
        if (mStartingWindow != null) {
            mStartingWindow.addStartingWindow(info, appToken);
        }
    }

# StartingWindowController
    private final StartingSurfaceDrawer mStartingSurfaceDrawer;

    public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
        mSplashScreenExecutor.execute(() -> {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
            ......
            // 获取当前 StartWindow 的类型
            final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType(
                    windowInfo);
            ......
            if (isSplashScreenType(suggestionType)) {
                // 主流程
                mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
                        suggestionType);
            }......//忽略
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        });
    }

这这个方法就是根据当前信息获取一个 StartWindow 的类型,一共定义了下面3种类型:

cpp 复制代码
# StartingWindowInfo
    /**
     * Prefer nothing or not care the type of starting window.
     * 没有类型
     * @hide
     */
    public static final int STARTING_WINDOW_TYPE_NONE = 0;

    /**
     * Prefer splash screen starting window.
     * 闪屏类型,冷启动用到
     * @hide 
     */
    public static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 1;
    /**
     * Prefer snapshot starting window.
     * 应用快照类型,那肯定不是冷启动,应该是多任务切换时相关
     * @hide
     */
    public static final int STARTING_WINDOW_TYPE_SNAPSHOT = 2;

当前冷启动场景的类型肯定是 STARTING_WINDOW_TYPE_SPLASH_SCREEN ,根据打印的日志也能确定。

cpp 复制代码
	ShellStartingWindow: preferredStartingWindowType newTask=true, taskSwitch=true, processRunning=false, allowTaskSnapshot=true, activityCreated=false, isSolidColorSplashScreen=false, legacySplashScreen=false, activityDrawn=false, topIsHome=false

接下来是核心代码了,这里构建StartWindow的参数。

2.2.1 构建 StartWindow 的参数

这个方法 StartWindow 显示的核心方法,内部做了很多事。

cpp 复制代码
# StartingSurfaceDrawer 

    void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
            @StartingWindowType int suggestType) {
                ......
                // replace with the default theme if the application didn't set
                // 拿到主题资源
                final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
                // 打印日志
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                        "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
                        activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
                    ......
                    // 设置主题
                    context.setTheme(theme);
                    ......
                //  开始构建需要的参数
                final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
    
                ......
                // window 动画资源
                params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
                ......
                // 这个token是system_service传过来的,也就是目标目标应用的ActivityRecord的token
                params.token = appToken;
                params.packageName = activityInfo.packageName;
                ......
                // 在这设置name
                params.setTitle("Splash Screen " + activityInfo.packageName);
                ......
                // 构建一个 SplashScreenViewSupplier ,内部保存真正是 StartWindow 的 ContentView
                final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
                // 定义一个跟布局,可以理解为 rootView (Activity 有个 rootView 然后下面才是contentView)
                final FrameLayout rootLayout = new FrameLayout(mSplashscreenContentDrawer.createViewContextWrapper(context));
                rootLayout.setPadding(0, 0, 0, 0);
                rootLayout.setFitsSystemWindows(false);
                // 定义一个 Runnable, 作为下一帧 Vsync 来临执行的回调。 也是
                final Runnable setViewSynchronized = () -> {
                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
                    // waiting for setContentView before relayoutWindow
                    // 拿到保存在 SplashScreenViewSupplier 下的 contentView
                    SplashScreenView contentView = viewSupplier.get();
                    // 获取到 StartingWindowRecord
                    final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
                    // If record == null, either the starting window added fail or removed already.
                    // Do not add this view if the token is mismatch.
                    if (record != null && appToken == record.mAppToken) {
                        // if view == null then creation of content view was failed.
                        if (contentView != null) {
                            try {
                                // 重点,把 contentView 设置到 rootView下面
                                rootLayout.addView(contentView);
                            } catch (RuntimeException e) {
                                Slog.w(TAG, "failed set content view to starting window "
                                        + "at taskId: " + taskId, e);
                                contentView = null;
                            }
                        }
                        // 把 contentView 保存进去,copySplashScreenView 方法使用 (目前没发现场景,可以忽略)
                        record.setSplashScreenView(contentView);
                    }
                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                };
                ......
                // 重点* 1. 创建具体的内容
                mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
                    viewSupplier::setView, viewSupplier::setUiThreadInitTask);
                try {   
                    // 重点* 2. 主流程 (2.3 小节介绍)
                    if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
                        ......
                    } else ......
                } ......
                ......
            }

这里有2个重点:

1.构建 StartWindow 的 ContentView

2.构建 window 参数然后执行 addWindow 流程

StartWindow 的 addWindow 流程 在 2.3 小节看, 下面先看一下怎么构建 StartWindow 的 ContentView 。

2.2.2 构建startWindow的ContentView

根据调用看 SplashscreenContentDrawer 的方法,注意第4个参数,传进来的是一个回调 SplashScreenViewSupplier::setView ,最后创建的 contentView 传递到这个回调中了。

cpp 复制代码
# SplashscreenContentDrawer
    void createContentView(Context context, @StartingWindowType int suggestType,
            StartingWindowInfo info, Consumer<SplashScreenView> splashScreenViewConsumer,
            Consumer<Runnable> uiThreadInitConsumer) {
        mSplashscreenWorkerHandler.post(() -> {
            SplashScreenView contentView;
            try {
                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "makeSplashScreenContentView");
                // 重点* 创建contentView
                contentView = makeSplashScreenContentView(context, info, suggestType,
                        uiThreadInitConsumer);
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
            } catch (RuntimeException e) {
                Slog.w(TAG, "failed creating starting window content at taskId: "
                        + info.taskInfo.taskId, e);
                contentView = null;
            }
            // contentView就被设置到对SplashScreenViewSupplier中了
            splashScreenViewConsumer.accept(contentView);
        });
    }

private SplashScreenView makeSplashScreenContentView(Context context, StartingWindowInfo info,
            @StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
        updateDensity();
        // 重点* 属性参数在这里获取
        getWindowAttrs(context, mTmpAttrs);
        mLastPackageContextConfigHash = context.getResources().getConfiguration().hashCode();

        final Drawable legacyDrawable = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
                ? peekLegacySplashscreenContent(context, mTmpAttrs) : null;
        final ActivityInfo ai = info.targetActivityInfo != null
                ? info.targetActivityInfo
                : info.taskInfo.topActivityInfo;
        final int themeBGColor = legacyDrawable != null
                ? getBGColorFromCache(ai, () -> estimateWindowBGColor(legacyDrawable))
                : getBGColorFromCache(ai, () -> peekWindowBGColor(context, mTmpAttrs));
        // 最终构造出一个SplashScreenView返回
        return new StartingWindowViewBuilder(context, ai)
                .setWindowBGColor(themeBGColor)
                .overlayDrawable(legacyDrawable)
                .chooseStyle(suggestType)
                .setUiThreadInitConsumer(uiThreadInitConsumer)
                .setAllowHandleSolidColor(info.allowHandleSolidColorSplashScreen())
                .build();
    }

上面这2个方法,首先是调用 makeSplashScreenContentView 返回一个真正的 SplashScreenView ,然后再通过回调的方式设置到 SplashScreenViewSupplier 中保存,

至于 SplashScreenView 构造逻辑也不复杂,主要是在SplashscreenContentDrawer::getWindowAttrs方法中获取主题中的资源,然后在通过build方式构建,这里看看getWindowAttrs方法。

2.2.2.1 StartWindow 的属性

这个方法会获取 StartWindow 的属性,如果需要修改 StartWindow 的显示,可以在应用中定义一个主题资源,修改上面代码里的这些属性达到不同的效果。

cpp 复制代码
# SplashscreenContentDrawer
    private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
        final TypedArray typedArray = context.obtainStyledAttributes(
                com.android.internal.R.styleable.Window);
        attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
        attrs.mWindowBgColor = safeReturnAttrDefault((def) -> typedArray.getColor(
                R.styleable.Window_windowSplashScreenBackground, def),
                Color.TRANSPARENT);
        attrs.mSplashScreenIcon = safeReturnAttrDefault((def) -> typedArray.getDrawable(
                R.styleable.Window_windowSplashScreenAnimatedIcon), null);
        attrs.mBrandingImage = safeReturnAttrDefault((def) -> typedArray.getDrawable(
                R.styleable.Window_windowSplashScreenBrandingImage), null);
        attrs.mIconBgColor = safeReturnAttrDefault((def) -> typedArray.getColor(
                R.styleable.Window_windowSplashScreenIconBackgroundColor, def),
                Color.TRANSPARENT);
        typedArray.recycle();
        // 打印log
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                "getWindowAttrs: window attributes color: %s, replace icon: %b",
                Integer.toHexString(attrs.mWindowBgColor), attrs.mSplashScreenIcon != null);
    }

这里打印第3个ShellStartingWindow log,看出来用的就是默认的主题。

cpp 复制代码
	ShellStartingWindow: getWindowAttrs: window attributes color: 0, replace icon: false
2.2.2.2 构建 SplashScreenView

StartingWindowViewBuilder 通过 build 模式会构建返回一个 SplashScreenView 对象,具体的就不看了,主要关注一下3个输出 proto 日志的地方。

这里就是看一下 StartingWindowViewBuilder 的几个 build 模式是咋处理的。

cpp 复制代码
# StartingWindowViewBuilder

        SplashScreenView build() {
             // 定义图标Drawable
            Drawable iconDrawable;
            
            if (mSuggestType == STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN
                    || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
                ......
            } else if (mTmpAttrs.mSplashScreenIcon != null) {
                ......
            } else {
                // 计算图标缩放比例
                final float iconScale = (float) mIconSize / (float) mDefaultIconSize;
                // 获取设备的密度DPI
                final int densityDpi = mContext.getResources().getConfiguration().densityDpi;
                 // 计算缩放后图标DPI
                final int scaledIconDpi =
                        (int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
                // Trace
                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon");
                // 获取图标
                iconDrawable = mHighResIconProvider.getIcon(
                        mActivityInfo, densityDpi, scaledIconDpi);
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                if (!processAdaptiveIcon(iconDrawable)) {
                    // 正常没有这个日志打印,说明不执行进来
                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                            "The icon is not an AdaptiveIconDrawable");
                    ......
                }
            }
            // 使用图标大小、图标绘制对象和UI线程初始化任务填充视图并返回
            return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, mUiThreadInitTask); 
        }
cpp 复制代码
# StartingWindowViewBuilder

        private boolean processAdaptiveIcon(Drawable iconDrawable) {
            if (!(iconDrawable instanceof AdaptiveIconDrawable)) {
                return false;
            }
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon");
            final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) iconDrawable;
            final Drawable iconForeground = adaptiveIconDrawable.getForeground();
            final ColorCache.IconColor iconColor = mColorCache.getIconColor(
                    mActivityInfo.packageName, mActivityInfo.getIconResource(),
                    mLastPackageContextConfigHash,
                    () -> new DrawableColorTester(iconForeground,
                            DrawableColorTester.TRANSLUCENT_FILTER /* filterType */),
                    () -> new DrawableColorTester(adaptiveIconDrawable.getBackground()));
            // 日志
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                    "processAdaptiveIcon: FgMainColor=%s, BgMainColor=%s, "
                            + "IsBgComplex=%b, FromCache=%b, ThemeColor=%s",
                    Integer.toHexString(iconColor.mFgColor),
                    Integer.toHexString(iconColor.mBgColor),
                    iconColor.mIsBgComplex,
                    iconColor.mReuseCount > 0,
                    Integer.toHexString(mThemeColor));
            if (!iconColor.mIsBgComplex && mTmpAttrs.mIconBgColor == Color.TRANSPARENT
                    && (isRgbSimilarInHsv(mThemeColor, iconColor.mBgColor)
                            || (iconColor.mIsBgGrayscale
                                    && !isRgbSimilarInHsv(mThemeColor, iconColor.mFgColor)))) {
                // 日志
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                        "processAdaptiveIcon: choose fg icon");

            }
        }

        private static boolean isRgbSimilarInHsv(int a, int b) {
            ......
            // 日志
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                "isRgbSimilarInHsv a:%s, b:%s, contrast ratio:%f",
                Integer.toHexString(a), Integer.toHexString(b), contrastRatio);
            ......
        }   

这里都是一些构建 SplashScreenView 相关参数的日志打印,一般不会有啥问题,知道在哪执行就好,具体内容我觉得可以忽略,有问题再看。

2.2.3 保存 SplashScreenView

cpp 复制代码
# StartingSurfaceDrawer

    private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
            // 闪屏页用到的View
            private SplashScreenView mView;

            private boolean mIsViewSet;

            private Runnable mUiThreadInitTask;

            // 设置SplashScreenView
            void setView(SplashScreenView view) {
                synchronized (this) {
                    mView = view;
                    mIsViewSet = true;
                    notify();
                }
            }

            void setUiThreadInitTask(Runnable initTask) {
                synchronized (this) {
                    mUiThreadInitTask = initTask;
                }
            }

            // 获取SplashScreenView
            @Override
            public @Nullable SplashScreenView get() {
                synchronized (this) {
                    while (!mIsViewSet) {
                        try {
                            wait();
                        } catch (InterruptedException ignored) {
                        }
                    }
                    if (mUiThreadInitTask != null) {
                        mUiThreadInitTask.run();
                        mUiThreadInitTask = null;
                    }
                    return mView;
                }
            }
        }

也就是说这个StartWindow 的 View 被保存到了 SplashScreenViewSupplier 下的 mView 对象中

2.3 system_service 执行 StartWindow的addWindow流程

上面小节是构建 StartWindow 用到的 contentView ,现在来看看怎么使用的。

这里再回顾一下2个分支的分歧点, 是在 StartingSurfaceDrawer::addSplashScreenStartingWindow 方法。

cpp 复制代码
# StartingSurfaceDrawer 

    void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
            @StartingWindowType int suggestType) {
                ......
                // replace with the default theme if the application didn't set
                // 拿到主题资源
                final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
                // 打印日志
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                        "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
                        activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
                    ......
                    // 设置主题
                    context.setTheme(theme);
                    ......
                //  开始构建需要的参数
                final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);//构造LayoutParams,类型为TYPE_APPLICATION_STARTING
                ......
                // window 动画资源
                params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
                ......
                // 这个token是system_service传过来的,也就是目标目标应用的ActivityRecord的token
                params.token = appToken;
                params.packageName = activityInfo.packageName;
                ......
                // 在这设置name
                params.setTitle("Splash Screen " + activityInfo.packageName);
                ......
                // 构建一个 SplashScreenViewSupplier ,内部保存真正是 StartWindow 的 ContentView
                final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
                // 定义一个跟布局,可以理解为 rootView (Activity 有个 rootView 然后下面才是contentView)
                final FrameLayout rootLayout = new FrameLayout(mSplashscreenContentDrawer.createViewContextWrapper(context));
                rootLayout.setPadding(0, 0, 0, 0);
                rootLayout.setFitsSystemWindows(false);
                // 定义一个 Runnable, 作为下一帧 Vsync 来临执行的回调。 也是
                final Runnable setViewSynchronized = () -> {
                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
                    // waiting for setContentView before relayoutWindow
                    // 拿到保存在 SplashScreenViewSupplier 下的 contentView
                    SplashScreenView contentView = viewSupplier.get();
                    // 获取到 StartingWindowRecord
                    final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
                    // If record == null, either the starting window added fail or removed already.
                    // Do not add this view if the token is mismatch.
                    if (record != null && appToken == record.mAppToken) {
                        // if view == null then creation of content view was failed.
                        if (contentView != null) {
                            try {
                                // 重点,把 contentView 设置到 rootView下
                                rootLayout.addView(contentView);
                            } catch (RuntimeException e) {
                                Slog.w(TAG, "failed set content view to starting window "
                                        + "at taskId: " + taskId, e);
                                contentView = null;
                            }
                        }
                        // 把 contentView 保存进去,copySplashScreenView 方法使用(应用自定义执行 SplashScreen::setOnExitAnimationListener 会触发)
                        record.setSplashScreenView(contentView);
                    }
                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                };
                ......
                // 重点* 1. 创建具体的内容
                mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
                    viewSupplier::setView, viewSupplier::setUiThreadInitTask);
                try {   
                    // 重点* 2. 主流程 (2.3 小节介绍)
                    if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
                        ......
                    } else ......
                } ......
                ......
            }

现在来看看 addWindow 方法做了什么,首先来看一下这几个参数:

  • taskId : ActivityRecord 所在的Task
  • appToken : 就是 ActivityRecord ,说明 StartWindow 和应用窗口本身挂载的其实是一个父节点
  • rootLayout: 这个就是rootView了,StartWindow 的 contentView 也被设置了进去。
  • display:默认一个屏幕的话,那就是主屏幕ID了
  • params :contentView 的参数
  • suggestType :StartWindow 的类型
    弄清这些参数后,再看 addWindow 方法,目的是要把 StartWindow 挂载到窗口树上了,代码如下:
cpp 复制代码
# StartingSurfaceDrawer 
    protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
            WindowManager.LayoutParams params, @StartingWindowType int suggestType) {
                try {
                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
                    // 开始触发addView
                    mWindowManagerGlobal.addView(view, params, display,
                            null /* parentWindow */, context.getUserId());
                }......

            }

后面也是通过 WindowManagerGlobal::addView 执行 addWindow 流程,这个和应用的 addWindow 流程是差不多的。

后面就是到WMS了也就是到system_service进程,但是具体在WMS的 addWindow 方法,针对 startWindow 的处理也是有区别的,其他的一样的就不看了,看对当前type:TYPE_APPLICATION_STARTING 的处理

cpp 复制代码
# WindowManagerService
    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
                ......// 省略token, windowState相关
                if (type == TYPE_APPLICATION_STARTING && activity != null) {
                    // 将其加到应用 ActivityRecord下,然后打印log
                    activity.attachStartingWindow(win);
                    ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
                            activity, win);
                } 
            }

# ActivityRecord
    // 这里就是将ActivityRecord和startingWindow进行一些绑定处理。
    void attachStartingWindow(@NonNull WindowState startingWindow) {
        // 将之前创建的mStartingData 给 startingWindow
        startingWindow.mStartingData = mStartingData;
        // 将startingWindow保存到ActivityRecord
        mStartingWindow = startingWindow;
        // 后面不执行
        if (mStartingData != null && mStartingData.mAssociatedTask != null) {
            attachStartingSurfaceToAssociatedTask();
        }
    }

到这里 ActivityRecord 中的 mStartingWindow 就有值了,后面的 mStartingData.mAssociatedTask 没有赋值,debug 的到也是为 null,所以if语句里面的不会执行。

其他的流程和addWindow流程一样了。

3. StartingWindow 的移除

StartingWindow 的移除其实就是在应用窗口绘制完成后,

WindowState::performShowLocked -- 窗口状态设置成 HAS_DRAWN

ActivityRecord::onFirstWindowDrawn -- 触发移除 StartWindow

下面会详细介绍移除 StartWindow 的流程(主要是移除动画)。
3.1 初步分析触发时机

[ShellStartingWindow的日志分析]

remove的日志开始是这段

cpp 复制代码
	ShellStartingWindow: Task start finish, remove starting surface for task: 28
	ShellStartingWindow: Removing splash screen window for task: 28

在代码里找到这2句日志是在StartingSurfaceDrawer类下的removeStartingWindow和removeWindowSynced方法里调用的,这一块

TaskOrganizer.mInterface::removeStartingWindow

ShellTaskOrganizer::removeStartingWindow

StartingSurfaceDrawer::removeStartingWindow

StartingSurfaceDrawer::removeWindowSynced

发现这个流程和【starting_reveal动画】前面的是一样的

cpp 复制代码
10-21 22:46:29.378 27972 27993 E biubiubiu: SurfaceAnimator  createAnimationLeash: starting_reveal
10-21 22:46:29.378 27972 27993 E biubiubiu: java.lang.Exception
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.SurfaceAnimator.createAnimationLeash(SurfaceAnimator.java:458)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.SurfaceAnimator.startAnimation(SurfaceAnimator.java:184)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2770)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2777)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2783)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.TaskOrganizerController.applyStartingWindowAnimation(TaskOrganizerController.java:490)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.TaskOrganizerController.removeStartingWindow(TaskOrganizerController.java:546)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.StartingSurfaceController$StartingSurface.remove(StartingSurfaceController.java:267)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.ActivityRecord.lambda$removeStartingWindowAnimation$6(ActivityRecord.java:2774)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.ActivityRecord$$ExternalSyntheticLambda9.run(Unknown Source:6)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.ActivityRecord.removeStartingWindowAnimation(ActivityRecord.java:2780)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.ActivityRecord.removeStartingWindow(ActivityRecord.java:2715)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.ActivityRecord.onFirstWindowDrawn(ActivityRecord.java:6438)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowState.performShowLocked(WindowState.java:4708)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowStateAnimator.commitFinishDrawingLocked(WindowStateAnimator.java:282)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.DisplayContent.lambda$new$8$com-android-server-wm-DisplayContent(DisplayContent.java:995)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.DisplayContent$$ExternalSyntheticLambda14.accept(Unknown Source:4)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer$ForAllWindowsConsumerWrapper.apply(WindowContainer.java:2642)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer$ForAllWindowsConsumerWrapper.apply(WindowContainer.java:2632)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowState.applyInOrderWithImeWindows(WindowState.java:4976)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowState.forAllWindows(WindowState.java:4820)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1646)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.DisplayContent.applySurfaceChangesTransaction(DisplayContent.java:4700)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.RootWindowContainer.applySurfaceChangesTransaction(RootWindowContainer.java:1027)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:828)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:788)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:178)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:126)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:115)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer$Traverser.run(WindowSurfacePlacer.java:57)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at android.os.Handler.handleCallback(Handler.java:942)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at android.os.Handler.dispatchMessage(Handler.java:99)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at android.os.Looper.loopOnce(Looper.java:210)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at android.os.Looper.loop(Looper.java:297)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at android.os.HandlerThread.run(HandlerThread.java:67)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.ServiceThread.run(ServiceThread.java:44)

整理后调用链如下:

cpp 复制代码
RootWindowContainer::performSurfacePlacementNoTrace
    RootWindowContainer::applySurfaceChangesTransaction
        DisplayContent::applySurfaceChangesTransaction
            DisplayContent::mApplySurfaceChangesTransaction  
                WindowStateAnimator::commitFinishDrawingLocked   -- READY_TO_SHOW
                    WindowState::performShowLocked   -- HAS_DRAWN
                        ActivityRecord::onFirstWindowDrawn -- 移除StartWindow,开始starting_reveal动画流程 
                            ActivityRecord::removeStartingWindow
                                ActivityRecord::removeStartingWindowAnimation
                                    StartingSurfaceController.StartingSurface::remove
                                        TaskOrganizerController::removeStartingWindow
                                            TaskOrganizerController::applyStartingWindowAnimation -- leash图层相关
                                                WindowContainer::startAnimation
                                                     WindowContainer::startAnimation
                                                         WindowContainer::startAnimation
                                                            SurfaceAnimator::startAnimation   --创建leash图层
                                                                SurfaceAnimator.createAnimationLeash
                                                                StartingWindowAnimationAdaptor::startAnimation
                                            ITaskOrganizer.removeStartingWindow    -- 跨进程通知SystemUI处理( ShellTaskOrganizer 远端动画)

这里重点看下

cpp 复制代码
//ActivityRecord.java
void removeStartingWindow() {
        boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
        //splash画面关闭时默认直接消失,如果进行了其动画定义,则会进入到这里面
        if (transferSplashScreenIfNeeded()) {
            return;
        }
        removeStartingWindowAnimation(true /* prepareAnimation */);

        final Task task = getTask();
        if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation()
                && task != null) {
            // Trigger TaskInfoChanged to update the letterbox education.
            task.dispatchTaskInfoChangedIfNeeded(true /* force */);
        }
    }


private boolean transferSplashScreenIfNeeded() {
        //这里主要看mHandleExitSplashScreen 这个参数
        if (finishing || !mHandleExitSplashScreen || mStartingSurface == null
                || mStartingWindow == null
                || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) {
            return false;
        }
        if (isTransferringSplashScreen()) {
            return true;
        }
        requestCopySplashScreen();
        return isTransferringSplashScreen();
    }

上面方法主要看mHandleExitSplashScreen 这个参数,我们看看这个参数的由来

cpp 复制代码
//ResumeActivityItem.java
public void postExecute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        // TODO(lifecycler): Use interface callback instead of actual implementation.
        ActivityClient.getInstance().activityResumed(token, client.isHandleSplashScreenExit(token));
    }

//ActivityClientController.java
    public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
        final long origId = Binder.clearCallingIdentity();
        synchronized (mGlobalLock) {
            ActivityRecord.activityResumedLocked(token, handleSplashScreenExit);
        }
        Binder.restoreCallingIdentity(origId);
    }

//ActivityRecord.java
static void activityResumedLocked(IBinder token, boolean handleSplashScreenExit) {
       ...
        r.setCustomizeSplashScreenExitAnimation(handleSplashScreenExit);
       ...
    }

client.isHandleSplashScreenExit函数的定义

cpp 复制代码
//位于ActivityThread.java
public boolean isHandleSplashScreenExit(@NonNull IBinder token) {
        synchronized (this) {
            return mSplashScreenGlobal != null && mSplashScreenGlobal.containsExitListener(token);
        }
    }

//SplashScreen.java
public boolean containsExitListener(IBinder token) {
            synchronized (mGlobalLock) {
                final SplashScreenImpl impl = findImpl(token);
                return impl != null && impl.mExitAnimationListener != null;
            }
        }

然后最后发现是在setOnExitAnimationListener函数中将其添加进来。

cpp 复制代码
//SplashScreen.java
public void setOnExitAnimationListener(
                @NonNull SplashScreen.OnExitAnimationListener listener) {
            if (mActivityToken == null) {
                // This is not an activity.
                return;
            }
            synchronized (mGlobal.mGlobalLock) {
                if (listener != null) {
                    mExitAnimationListener = listener;
                    mGlobal.addImpl(this);
                }
            }
        }

请求拷贝splashScreen

cpp 复制代码
//ActivityRecord.java
private void requestCopySplashScreen() {
        mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_COPYING;
        if (!mAtmService.mTaskOrganizerController.copySplashScreenView(getTask())) {
            mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
            removeStartingWindow();
        }
        scheduleTransferSplashScreenTimeout();
    }
cpp 复制代码
 //TaskOrganizerController.java
 boolean copySplashScreenView(Task task) {
       ...
        try {
            lastOrganizer.copySplashScreenView(task.mTaskId);//调用到systemui侧
        } catch (RemoteException e) {
            Slog.e(TAG, "Exception sending copyStartingWindowView callback", e);
            return false;
        }
        return true;
    }

// ShellTaskOrganizer.java
public void copySplashScreenView(int taskId) {
        if (mStartingWindow != null) {
            mStartingWindow.copySplashScreenView(taskId);
        }
    }

//StartingWindowController.java
public void copySplashScreenView(int taskId) {
        mSplashScreenExecutor.execute(() -> {
            mStartingSurfaceDrawer.copySplashScreenView(taskId);
        });
    }



public void copySplashScreenView(int taskId) {
        final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
        SplashScreenViewParcelable parcelable;
        SplashScreenView splashScreenView = preView != null ? preView.mContentView : null;
        if (splashScreenView != null && splashScreenView.isCopyable()) {
            parcelable = new SplashScreenViewParcelable(splashScreenView);//将spalshScreen进行打包
            parcelable.setClientCallback(
                    new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
                            () -> onAppSplashScreenViewRemoved(taskId, false))));
            splashScreenView.onCopied();
            mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
        } else {
            parcelable = null;
        }
       //发送到ATM进程
        ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
    }
//ActivityTaskManager.java
public void onSplashScreenViewCopyFinished(int taskId,
            @Nullable SplashScreenViewParcelable parcelable) {
        try {
            getService().onSplashScreenViewCopyFinished(taskId, parcelable);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
//ActivityTaskManagerService.java
 public void onSplashScreenViewCopyFinished(int taskId, SplashScreenViewParcelable parcelable)
            throws RemoteException {
        mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,
                "copySplashScreenViewFinish()");
        synchronized (mGlobalLock) {
            final Task task = mRootWindowContainer.anyTaskForId(taskId,
                    MATCH_ATTACHED_TASK_ONLY);
            if (task != null) {
                final ActivityRecord r = task.getTopWaitSplashScreenActivity();
                if (r != null) {
                    r.onCopySplashScreenFinish(parcelable);
                }
            }
        }
    }



    void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) {
        removeTransferSplashScreenTimeout();
        ...
        // schedule attach splashScreen to client
        final SurfaceControl windowAnimationLeash = TaskOrganizerController
                .applyStartingWindowAnimation(mStartingWindow);
        try {
            mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
            //这里会调用到TransferSplashScreenViewStateItem的execute
            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
                    TransferSplashScreenViewStateItem.obtain(parcelable,
                            windowAnimationLeash));
            scheduleTransferSplashScreenTimeout();
        ...
    }

// TransferSplashScreenViewStateItem.java
public void execute(@NonNull ClientTransactionHandler client,
            @NonNull ActivityThread.ActivityClientRecord r,
            PendingTransactionActions pendingActions) {
        client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable, mStartingWindowLeash);
    }

//ActivityThread.java
public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
            @Nullable SplashScreenView.SplashScreenViewParcelable parcelable,
            @NonNull SurfaceControl startingWindowLeash) {
        final DecorView decorView = (DecorView) r.window.peekDecorView();
        if (parcelable != null && decorView != null) {
            createSplashScreen(r, decorView, parcelable, startingWindowLeash);
        } else {
            // shouldn't happen!
            Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
        }
    }
    
    //ActivityThread.java
    private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
            SplashScreenView.SplashScreenViewParcelable parcelable,
            @NonNull SurfaceControl startingWindowLeash) {
        final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
        //这里构建了splashview
        final SplashScreenView view = builder.createFromParcel(parcelable).build();
        view.attachHostWindow(r.window);
        //添加到decorview中
        decorView.addView(view);
        view.requestLayout();

        view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
            private boolean mHandled = false;
            @Override
            public void onDraw() {
                if (mHandled) {
                    return;
                }
                mHandled = true;
                // Transfer the splash screen view from shell to client.
                // Call syncTransferSplashscreenViewTransaction at the first onDraw so we can ensure
                // the client view is ready to show and we can use applyTransactionOnDraw to make
                // all transitions happen at the same frame.
                syncTransferSplashscreenViewTransaction(
                        view, r.token, decorView, startingWindowLeash);
                view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
            }
        });
    }

这里有个view树绘制的监听onDraw,分析里面的方法

cpp 复制代码
//ActivityThread.java
private void syncTransferSplashscreenViewTransaction(SplashScreenView view, IBinder token,
            View decorView, @NonNull SurfaceControl startingWindowLeash) {
        // Ensure splash screen view is shown before remove the splash screen window.
        // Once the copied splash screen view is onDrawn on decor view, use applyTransactionOnDraw
        // to ensure the transfer of surface view and hide starting window are happen at the same
        // frame.
        final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
        //隐藏startingWindowLeash
        transaction.hide(startingWindowLeash);

        decorView.getViewRootImpl().applyTransactionOnDraw(transaction);
        view.syncTransferSurfaceOnDraw();
        // Tell server we can remove the starting window
        decorView.postOnAnimation(() -> reportSplashscreenViewShown(token, view));
    }

//ActivityThread.java
 private void reportSplashscreenViewShown(IBinder token, SplashScreenView view) {
        //回调到wms端splashScreen在应用侧已经处理完成
        ActivityClient.getInstance().reportSplashScreenAttached(token);
        synchronized (this) {
            if (mSplashScreenGlobal != null) {
                mSplashScreenGlobal.handOverSplashScreenView(token, view);
            }
        }
    }
    
//ActivityClient.java
void reportSplashScreenAttached(IBinder token) {
        try {
            getActivityClientController().splashScreenAttached(token);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

//ActivityClientController.java
public void splashScreenAttached(IBinder token) {
        final long origId = Binder.clearCallingIdentity();
        synchronized (mGlobalLock) {
            //这里回调到AMS
            ActivityRecord.splashScreenAttachedLocked(token);
        }
        Binder.restoreCallingIdentity(origId);
    }

//ActivityRecord.java
static void splashScreenAttachedLocked(IBinder token) {
        final ActivityRecord r = ActivityRecord.forTokenLocked(token);
        if (r == null) {
            Slog.w(TAG, "splashScreenTransferredLocked cannot find activity");
            return;
        }
        r.onSplashScreenAttachComplete();
    }

//ActivityRecord.java
private void onSplashScreenAttachComplete() {
        removeTransferSplashScreenTimeout();
        // Client has draw the splash screen, so we can remove the starting window.
        if (mStartingWindow != null) {
            mStartingWindow.cancelAnimation();
            mStartingWindow.hide(false, false);
        }
        // no matter what, remove the starting window.
        mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
        removeStartingWindowAnimation(false /* prepareAnimation */);
    }

所以我们可以通过demo定义启动动画见SplashScreen的启动动画

以上流程总结如下:

3.2 SystemUI进程移除StartWidow

ShellTaskOrganizer::removeStartingWindow

StartingWindowController::removeStartingWindow

StartingSurfaceDrawer::removeWindowInner

WindowManagerGlobal::removeView

WindowManagerGlobal::removeViewLocked

ViewRootImpl::die

ViewRootImpl::doDie

ViewRootImpl::relayoutWindow

IWindowSession::relayout --后续由system_service执行

这段前面的逻辑去【starting_reveal动画】看,当前直接从StartingSurfaceDrawer::removeWindowInner开始分析

cpp 复制代码
# StartingSurfaceDrawer
    private void removeWindowInner(View decorView, boolean hideView) {
        if (mSysuiProxy != null) {
            mSysuiProxy.requestTopUi(false, TAG);
        }
        if (hideView) {
            // 设置GONE,这里的decorView就是startWindow的decorView
            decorView.setVisibility(View.GONE);
        }
        // 触发remove,第二个参数为false
        mWindowManagerGlobal.removeView(decorView, false /* immediate */);
    }
# WindowManagerGlobal
    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (root != null) {
            root.getImeFocusController().onWindowDismissed();
        }
        // 调用ViewRootImpl::die 移除
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                // 加入需要移除的集合
                mDyingViews.add(view);
            }
        }
    }
cpp 复制代码
# ViewRootImpl
    boolean die(boolean immediate) {
        if (ViewDebugManager.DEBUG_LIFECYCLE) {
            Log.v(mTag, "die: immediate = " + immediate + ", mIsInTraversal = " + mIsInTraversal
                    + ",mIsDrawing = " + mIsDrawing + ",this = " + this, new Throwable());
        }

        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        //immediate为false左右不走这
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        // 发送消息
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
    private void handleMessageImpl(Message msg) {
        ......
        case MSG_DIE: {
            doDie();
        }
        ......
    }

这里通过Handler发送消息,对于这个消息的处理也是只执行了doDie()方法,所以直接看这个方法就好了

cpp 复制代码
# ViewRootImpl
    void doDie() {
        ......
                if (mView != null) {
                    // 前面设置为GONE了
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            // 重点* 调用relayoutWindow
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(
                                    mWindow, null /* postDrawTransaction */, Integer.MAX_VALUE);
                            }
                        } catch (RemoteException e) {
                            Log.e(mTag, "RemoteException when finish draw window " + mWindow
                                    + " in " + this, e);
                        }
                    }

                    destroySurface();
                }
        ......
    }

这段方法主要就是调用了relayoutWindow,参数viewVisibility是 GONE

cpp 复制代码
# ViewRootImpl
    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
                ......
                relayoutResult = mWindowSession.relayout(mWindow, params,
                        requestedWidth, requestedHeight, viewVisibility,
                        insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                        mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                        mTempControls, mRelayoutBundle);
                ......
        }

上面的这段逻辑,主要就是将startWindow进行了移除,然后通知WMS,执行一次relayout,后面就会触发startWindow的移除动画了。 至于怎么最终确认是这次SystemUI进程调用的relayout触发的呢? 在2编加上log或者直接debug都可以确认。

相关推荐
云和数据.ChenGuang44 分钟前
Django 应用安装脚本 – 如何将应用添加到 INSTALLED_APPS 设置中 原创
数据库·django·sqlite
woshilys1 小时前
sql server 查询对象的修改时间
运维·数据库·sqlserver
Hacker_LaoYi1 小时前
SQL注入的那些面试题总结
数据库·sql
建投数据2 小时前
建投数据与腾讯云数据库TDSQL完成产品兼容性互认证
数据库·腾讯云
Hacker_LaoYi3 小时前
【渗透技术总结】SQL手工注入总结
数据库·sql
岁月变迁呀3 小时前
Redis梳理
数据库·redis·缓存
独行soc3 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)
数据库·sql·安全·web安全·漏洞挖掘·hw
你的微笑,乱了夏天4 小时前
linux centos 7 安装 mongodb7
数据库·mongodb
工业甲酰苯胺4 小时前
分布式系统架构:服务容错
数据库·架构
独行soc5 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘