Android V app 冷启动(3) 添加启动窗口View

744 阅读5分钟

在第一阶段启动中,当窗口层级树构建完成后,会通知 root task ,其下有一个正在启动的 Activity,Task 会决定是否为这个正在启动 Activity 添加启动窗口。 本文就来研究添加启动窗口的流程。

WM-Core 添加启动窗口

// Task.java

// r 是要启动的 activity
// topTask 指的是 launcher root task
// newTask 为 true
// isTaskSwitch 为 true
// options 启动 activity 参数,不为 null
// sourceRecord 指的是 launcher
void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask,
        boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) {
    // ...

    ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s "
                    + "callers: %s", r, task, new RuntimeException("here").fillInStackTrace());

    // ...
    
    // doShow 决定是否要显示启动窗口
    boolean doShow = true;
    if (newTask) {
        // 从 start u0 的 log 看,是包含这个 flag 的。但是,这不重要
        // 最终 doShow 还是 true
        if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
            resetTaskIfNeeded(r, r);
            doShow = topRunningNonDelayedActivityLocked(null) == r;
        }
    } else if (options != null && options.getAnimationType()
            == ActivityOptions.ANIM_SCENE_TRANSITION) {
        // ...
    }
    
    // ...
    
    if (r.mLaunchTaskBehind) {
        
    } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
        Task baseTask = r.getTask();
        
        // 当前 Task 下,已经拥有启动窗口的 ActivityRecord
        // null
        final ActivityRecord prev = baseTask.getActivity(
                a -> a.mStartingData != null && a.showToCurrentUser());
        
        mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask,
                isTaskSwitch, sourceRecord);
    }
}
// StartingSurfaceController.java

void showStartingWindow(ActivityRecord target, ActivityRecord prev,
        boolean newTask, boolean isTaskSwitch, ActivityRecord source) {
    if (mDeferringAddStartingWindow) {
        // ...
    } else {
        target.showStartingWindow(prev, newTask, isTaskSwitch, true /* startActivity */,
                source);
    }
}
// ActivityRecord.java

void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
        boolean startActivity, ActivityRecord sourceRecord) {
    showStartingWindow(prev, newTask, taskSwitch, isProcessRunning(), startActivity,
            sourceRecord, null /* candidateOptions */);
}

// prev 是 Task 下前一个拥有启动窗口的 ActivityRecord,此时为 null
// newTask 为 true
// TaskSwitch 为 true
// processRunning 代表 app 进程是否正在运行,此时为 false
// startActivity 为 true
// sourceRecord 指 Launcher
// candidateOptions 为 null
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
        boolean processRunning, boolean startActivity, ActivityRecord sourceRecord,
        ActivityOptions candidateOptions) {
    // ...
    
    // candidateOptions 此时为 null,
    // mPendingOptions 是启动 Activity 时,传入的参数
    final ActivityOptions startOptions = candidateOptions != null
            ? candidateOptions : mPendingOptions;
    // ...

    // 解析 splash screen theme
    final int splashScreenTheme = startActivity ? getSplashscreenTheme(startOptions) : 0;
    final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme,
            splashScreenTheme);

    // 启动窗口,显示图标还是纯色
    // false
    mSplashScreenStyleSolidColor = shouldUseSolidColorSplashScreen(sourceRecord, startActivity,
            startOptions, resolvedTheme);

    // 此时 ActivityRecord 的状态还是 INITIALING,app 端并没有创建 Activity
    // false
    final boolean activityCreated =
            mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal();
    
    // false
    // If this activity is just created and all activities below are finish, treat this
    // scenario as warm launch.
    final boolean newSingleActivity = !newTask && !activityCreated
            && task.getActivity((r) -> !r.finishing && r != this) == null;

    final boolean scheduled = addStartingWindow(packageName, resolvedTheme,
            prev, newTask || newSingleActivity, taskSwitch, processRunning,
            allowTaskSnapshot(), activityCreated, mSplashScreenStyleSolidColor, allDrawn);
    if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) {
        Slog.d(TAG, "Scheduled starting window for " + this);
    }
}


boolean addStartingWindow(String pkg, int resolvedTheme, ActivityRecord from, boolean newTask,
        boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot,
        boolean activityCreated, boolean isSimple,
        boolean activityAllDrawn) {
    // ...
    
    // 目前 ActivityRecord 下还没有任何窗口
    // null
    final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
    // ...

    // app 冷启动,此时还没有 TaskSnapshot
    // null
    final TaskSnapshot snapshot =
            mWmService.mTaskSnapshotController.getSnapshot(task.mTaskId, task.mUserId,
                    false /* restoreFromDisk */, false /* isLowResolution */);
                    
    // app 冷启动,启动窗口类型为 STARTING_WINDOW_TYPE_SPLASH_SCREEN
    final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
            allowTaskSnapshot, activityCreated, activityAllDrawn, snapshot);

    // false
    final boolean useLegacy = type == STARTING_WINDOW_TYPE_SPLASH_SCREEN
            && mWmService.mStartingSurfaceController.isExceptionApp(packageName, mTargetSdk,
                () -> {
                    ActivityInfo activityInfo = intent.resolveActivityInfo(
                            mAtmService.mContext.getPackageManager(),
                            PackageManager.GET_META_DATA);
                    return activityInfo != null ? activityInfo.applicationInfo : null;
                });

    // 把所有启动窗口数据转化为一个 int
    final int typeParameter = StartingSurfaceController
            .makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning,
                    allowTaskSnapshot, activityCreated, isSimple, useLegacy, activityAllDrawn,
                    type, isIconStylePreferred(resolvedTheme), packageName, mUserId);

    // ...

    ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");
    
    // ActivityRecord.mStartingData 代表启动窗口数据
    // SplashScreenStartingData 就是一个简单的数据包装类
    mStartingData = new SplashScreenStartingData(mWmService, resolvedTheme, typeParameter);
    
    scheduleAddStartingWindow();
    return true;
}

private void scheduleAddStartingWindow() {
    ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s",
            this, mStartingData);

    // ActivityRecord.StartingSurface 象征性代表启动窗口 surface,并非实际的 surface
    mStartingSurface = mStartingData.createStartingSurface(ActivityRecord.this);
    
    if (mStartingSurface != null) {
        ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
                "Added starting %s: startingWindow=%s startingView=%s",
                ActivityRecord.this, mStartingWindow, mStartingSurface);
    } else {
        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Surface returned was null: %s",
                ActivityRecord.this);
    }
}
// SplashScreenStartingData.java

class SplashScreenStartingData extends StartingData {
    @Override
    StartingSurface createStartingSurface(ActivityRecord activity) {
        return mService.mStartingSurfaceController.createSplashScreenStartingSurface(
                activity, mTheme);
    }
}
// StartingSurfaceController.java

StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, int theme) {
    final Task task = activity.getTask();
    final TaskOrganizerController controller =
            mService.mAtmService.mTaskOrganizerController;
            
    // TaskOrganizerController 处理添加启动窗口
    if (task != null && controller.addStartingWindow(task, activity, theme,
            null /* taskSnapshot */)) {
		// StartingSurface 被象征性的代表启动窗口 surface,实际上它只是一个数据封装类
        return new StartingSurface(task, controller.getTaskOrganizer());
    }
    return null;
}
// TaskOrganizerController.java

boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
        TaskSnapshot taskSnapshot) {
    final Task rootTask = task.getRootTask();
    if (rootTask == null || activity.mStartingData == null) {
        return false;
    }
    final ITaskOrganizer lastOrganizer = getTaskOrganizer();
    if (lastOrganizer == null) {
        return false;
    }
    
    // 把所有启动窗口的相关数据,都保存到 StartingWindowInfo
    final StartingWindowInfo info = task.getStartingWindowInfo(activity);
    if (launchTheme != 0) {
        info.splashScreenThemeResId = launchTheme;
    }
    info.taskSnapshot = taskSnapshot;// null
    info.appToken = activity.token;
    
    
    try {
        // 通知 WMShell 添加启动窗口
        lastOrganizer.addStartingWindow(info);
    } catch (RemoteException e) {
        Slog.e(TAG, "Exception sending onTaskStart callback", e);
        return false;
    }
    return true;
}

WM-Shell 添加启动窗口

// ShellTaskOrganizer.java

public void addStartingWindow(StartingWindowInfo info) {
    if (mStartingWindow != null) {
        mStartingWindow.addStartingWindow(info);
    }
}
// StartingWindowController.java

public void addStartingWindow(StartingWindowInfo windowInfo) {
    mSplashScreenExecutor.execute(() -> {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
        
        // 值为 STARTING_WINDOW_TYPE_SPLASH_SCREEN
        final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType(
                windowInfo);
        
        final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
        if (suggestionType == STARTING_WINDOW_TYPE_WINDOWLESS) {
            // ...
        } else if (isSplashScreenType(suggestionType)) {
            mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType);
        } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
            // ...
        }

        // ..
        
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    });
}
// StartingSurfaceDrawer.java

void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
        @StartingWindowType int suggestType) {
    mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType);
}
// SplashScreenWindowCreator.java

void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
        @StartingWindowInfo.StartingWindowType int suggestType) {
    final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
    final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
            ? windowInfo.targetActivityInfo
            : taskInfo.topActivityInfo;
    if (activityInfo == null || activityInfo.packageName == null) {
        return;
    }
    // replace with the default theme if the application didn't set
    final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
    final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
            suggestType, mDisplayManager);
    if (context == null) {
        return;
    }
    
    // 构建 LayoutParams
    final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
            context, windowInfo, suggestType, activityInfo.packageName,
            suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
                    ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, windowInfo.appToken);

    final int displayId = taskInfo.displayId;
    final int taskId = taskInfo.taskId;
    final Display display = getDisplay(displayId);

    // TODO(b/173975965) tracking performance
    // Prepare the splash screen content view on splash screen worker thread in parallel, so the
    // content view won't be blocked by binder call like addWindow and relayout.
    // 1. Trigger splash screen worker thread to create SplashScreenView before/while
    // Session#addWindow.
    // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
    // traversal, which will call Session#relayout on splash screen thread.
    // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
    // the same time the splash screen thread should be executing Session#relayout. Blocking the
    // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.

    // Record whether create splash screen view success, notify to current thread after
    // create splash screen view finished.
    
    // SplashScreenViewSupplier 是为了缓存 worker thread 中创建的启动窗口 View
    final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
    
    // 创建启动窗口的根 View
    final FrameLayout rootLayout = new FrameLayout(
            mSplashscreenContentDrawer.createViewContextWrapper(context));
    rootLayout.setPadding(0, 0, 0, 0);
    rootLayout.setFitsSystemWindows(false);
    
    // 这个 Runnable 是为了同步地获取 worker thread 中创建的启动窗口 View,并添加到根 View 中
    final Runnable setViewSynchronized = () -> {
        // trace 记录了同步并添加 view 的过程
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
        
        // waiting for setContentView before relayoutWindow
        // 阻塞式的同步 worker thread 创建的启动窗口 View
        SplashScreenView contentView = viewSupplier.get();
        
        final StartingSurfaceDrawer.StartingWindowRecord sRecord =
                mStartingWindowRecordManager.getRecord(taskId);
        final SplashWindowRecord record = sRecord instanceof SplashWindowRecord
                ? (SplashWindowRecord) sRecord : null;
                

        if (record != null && windowInfo.appToken == record.mAppToken) {
            if (contentView != null) {
                try {
                    // 把启动窗口 View 保存到根 View 中
                    rootLayout.addView(contentView);
                } catch (RuntimeException e) {
                    // ...
                }
            }
            record.setSplashScreenView(contentView);
        }
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    };
    
    requestTopUi(true);
    
    // 1. splash screen thread 中异步创建 splash screen view
    mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
            viewSupplier::setView, viewSupplier::setUiThreadInitTask);
            
            
    try {
        // 2. 通过 WindowManagerGlobal 添加启动窗口的根 View
        if (addWindow(taskId, windowInfo.appToken, rootLayout, display, params, suggestType)) {
            // We use the splash screen worker thread to create SplashScreenView while adding
            // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
            // And since Choreographer#doFrame won't happen immediately after adding the window,
            // if the view is not added to the PhoneWindow on the first #doFrame, the view will
            // not be rendered on the first frame. So here we need to synchronize the view on
            // the window before first round relayoutWindow, which will happen after insets
            // animation.
            // 3. 请求 Vsync 信号,在下一帧的回调中,把 worker thread 中创建的启动窗口 View,
            // 同步到当前线程,并添加到根 View 中
            mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
            
            // ...
        } else {
            // ...
        }
    } catch (RuntimeException e) {
        // ...
    }
}
  1. 在 splash screen worker thread 中创建启动窗口 View。
  2. 在 splash screen thread 中,通过 WindowManagerGlobal 添加启动窗口的根 View。ViewRootImpl 会向 Choreographer 注册一个类型为 CALLBACK_TRAVERSAL 的回调,用于执行 traversal,traversal 中会 relayoutwindow 并完成绘制。
  3. 在 splash screen thread 中,向 Choreographer 注册一个 CALLBACK_INSETS_ANIMATION 的回调,用于同步 worker thread 中创建启动窗口 View,并添加到根 View 中。

当 Vsyn 信号到来,会先执行 CALLBACK_INSETS_ANIMATION 类型的回调,再执行 CALLBACK_TRAVERSAL 的回调。 因此,先执行第三步,把启动窗口 View 添加到根 View 中,然后执行第二步中的 traversal,完成启动窗口的绘制。