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

669 阅读10分钟

在第一阶段启动中,当窗口层级构建完成后,会利用 Task 来添加启动窗口,本文就来研究这个课题。

WMCore 添加启动窗口

// 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) {
    // ...

    final Task activityTask = r.getTask();
    
    // ...

    task = activityTask;

    // Slot the activity into the history root task and proceed
    ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s "
                    + "callers: %s", r, task, new RuntimeException("here").fillInStackTrace());

    // ...
    

    // 检测是否需要显示启动窗口
    boolean doShow = true;
    if (newTask) {
        // 从 start u0 的 log 看,是包含这个 flag 的
        if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
            // reset task 指的是把相关的 activity 移动到当前 task,或者把不相关 actvity 移出 task
            // 本文分析的案例不涉及
            resetTaskIfNeeded(r, r);
            // reset Task 后,如果 Task 的 top-non-delayed-non-finishing activity 仍然是要启动的 activity
            // 那么,需要显示启动窗口
            doShow = topRunningNonDelayedActivityLocked(null) == r;
        }
    } else if (options != null && options.getAnimationType()
            == ActivityOptions.ANIM_SCENE_TRANSITION) {
        // ...
    }
    
    // 启动 activity 的 Bundle 参数,也可以 disable starting window
    // 本文分析的案例没有这个数据
    if (options != null && options.getDisableStartingWindow()) {
        doShow = false;
    }
    
    if (r.mLaunchTaskBehind) {
        
    } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
        // Figure out if we are transitioning from another activity that is
        // "has the same starting icon" as the next one.  This allows the
        // window manager to keep the previous window it had previously
        // created, if it still had one.
        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 {
        // 直接交给 ActivityRecord show start window
        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 */);
}

void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
        boolean processRunning, boolean startActivity, ActivityRecord sourceRecord,
        ActivityOptions candidateOptions) {
    if (mTaskOverlay) {
        // We don't show starting window for overlay activities.
        return;
    }
    final ActivityOptions startOptions = candidateOptions != null
            ? candidateOptions : mPendingOptions;
    if (startOptions != null
            && startOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
        // Don't show starting window when using shared element transition.
        return;
    }

    // 解析 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,并没有启动
    // 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) {

    if (!okToDisplay()) {
        return false;
    }

    if (hasStartingWindow()) {
        return false;
    }
    
    // 目前 ActivityRecord 下还没有窗口 WindowState
    final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
    if (mainWin != null && mainWin.isDrawn()) {
        // App already has a visible window...why would you want a starting window?
        return false;
    }

    // app 冷启动,系统还没有 Task 的快照
    // 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");
    
    // 创建启动窗口数据
    mStartingData = new SplashScreenStartingData(mWmService, resolvedTheme, typeParameter);
    
    // 执行下一步添加启动窗口
    scheduleAddStartingWindow();
    return true;
}
// ActivityRecord.java

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

    // 由 SplashScreenStartingData 创建 'starting window surface'
    // 但它并不是真正的 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,主要保存启动窗口的 Task
        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;
    info.appToken = activity.token;
    
    
    try {
        // 通知 WMShell 添加启动窗口
        lastOrganizer.addStartingWindow(info);
    } catch (RemoteException e) {
        Slog.e(TAG, "Exception sending onTaskStart callback", e);
        return false;
    }
    return true;
}

WMCore 添加启动窗口的流程,非常简单,没什么好解释的,展示下流程图,如下

startingwindow.jpg

WMcore 完成添加启动窗口后,ActivityRecord#mStartingData 保存了启动窗口相关的数据,例如,启动窗口类型。ActivityRecord#mStartingSurface 象征性的保存了启动窗口的 surface。

WMShell 创建启动窗口

// ShellTaskOrganizer.java

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

public void addStartingWindow(StartingWindowInfo windowInfo) {
    // 注意,这是在 splash screen thread 中执行的
    mSplashScreenExecutor.execute(() -> {
        // WMShell 添加启动窗口的 trace
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
        
        // 1. 计算启动窗口类型
        // 此时返回的是 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)) {
            // 2. 添加启动窗口
            mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType);
        } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
            // ...
        }

        if (suggestionType != STARTING_WINDOW_TYPE_NONE
                && suggestionType != STARTING_WINDOW_TYPE_WINDOWLESS) {
            int taskId = runningTaskInfo.taskId;
            int color = mStartingSurfaceDrawer
                    .getStartingWindowBackgroundColorForTask(taskId);
            if (color != Color.TRANSPARENT) {
                mTaskBackgroundColors.append(taskId, color);
            }

            if (mTaskLaunchingCallback != null && isSplashScreenType(suggestionType)) {
                mTaskLaunchingCallback.accept(taskId, suggestionType, color);
            }
        }
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    });
}
// StartingSurfaceDrawer.java

/**
 * A class which able to draw splash screen or snapshot as the starting window for a task.
 */
public class StartingSurfaceDrawer {

    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;
    }
    
    // 构建布局参数,为了通过 WindowManager 添加启动窗口 View
    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);

    // 注意下面一段注释,解释了添加启动窗口 View 的流程
    // 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. SplashScreenContentDrawer 在 worker thread 中创建启动窗口 View
    // 创建完成之后,会通过第四个参数,把 view 缓存到 ViewSupplier 中
    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) {
        // ...
    }
}

从根本上讲,就是创建启动窗口 View,然后通过 WindowManager 添加。但是,为了性能,采用了多线程来实现,具体流程如下

  1. 先在 splash screen worker thread 中创建启动窗口 View。
  2. 在 splash screen thread 中,通过 WindowManager 添加启动窗口的根 View。同时,ViewRootImpl 会向 Choreographer 注册一个类型为 CALLBACK_TRAVERSAL 的回调,用于执行 traversal,traversal 中会完成窗口的绘制。
  3. 在 splash screen thread 中,向 Choreographer 注册一个 CALLBACK_INSETS_ANIMATION 的回调,用于同步 worker thread 中的启动窗口 View,并添加到根 View 中。

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

创建启动窗口 View

创建启动窗口 View ,也有比较意思的地方,来看下它的设计原理

// SplashscreenContentDrawer.java

void createContentView(Context context, @StartingWindowType int suggestType,
        StartingWindowInfo info, Consumer<SplashScreenView> splashScreenViewConsumer,
        Consumer<Runnable> uiThreadInitConsumer) {
    // 在 splash screen worker thread 中创建启动窗口 View
    mSplashscreenWorkerHandler.post(() -> {
        SplashScreenView contentView;
        try {
            // 创建 SplashScreenView 的 trace
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "makeSplashScreenContentView");
            
            // 创建 SplashScreenView
            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;
        }
        
        //把 SplashScreenView 缓存到 SplashScreenViewSupplier 中
        splashScreenViewConsumer.accept(contentView);
    });
}

private SplashScreenView makeSplashScreenContentView(Context context, StartingWindowInfo info,
        @StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
    // 从系统配置中,读取一些数据,例如 icon size
    updateDensity();

    // 从 theme 中,获取启动窗口的属性,例如,启动窗口背景色,保存到 mTmpAttrs
    getWindowAttrs(context, mTmpAttrs);
    mLastPackageContextConfigHash = context.getResources().getConfiguration().hashCode();

    final @StartingWindowType int splashType =
            suggestType == STARTING_WINDOW_TYPE_SPLASH_SCREEN && !canUseIcon(info)
            ? STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN : suggestType;
    final Drawable legacyDrawable = splashType == 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));

    return new SplashViewBuilder(context, ai)
            .setWindowBGColor(themeBGColor)
            .overlayDrawable(legacyDrawable)
            .chooseStyle(splashType)
            .setUiThreadInitConsumer(uiThreadInitConsumer)
            .setAllowHandleSolidColor(info.allowHandleSolidColorSplashScreen())
            .build();
}


private class SplashViewBuilder {

    SplashScreenView build() {
        Drawable iconDrawable;
        if (mSuggestType == STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN
                || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
            // ...
        } else if (mTmpAttrs.mSplashScreenIcon != null) { // 假设在 theme 中指定了 splash screen icon
            // 获取 splash screen icon
            iconDrawable = mTmpAttrs.mSplashScreenIcon;
            
            // There is no background below the icon, so scale the icon up
            if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT
                    || mTmpAttrs.mIconBgColor == mThemeColor) {
                mFinalIconSize *= mNoBackgroundScale;
            }
            
            // 1.创建 foreground drawable 和 background drawable,并保存到数组 mFinalIconDrawables 中
            createIconDrawable(iconDrawable, false /* legacy */, false /* loadInDetail */);
        } else {
           // ...
        }

        // 2. 把 foreground drawable 和 background drawable 填充到 layout view 中
        return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, mUiThreadInitTask);
    }
}

为了方便分析,我们假设在 theme 中设置了 splash screen icon。然后,利用背景色创建 background drawable,用 splash screen icon 创建 foreground drawable。最后,把两个 drawable 填充到 layout view 中。

这里重点看下 foreground drawable 的创建,它才是设计的核心

// SplashscreenIconDrawableFactory.java

static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
        @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
        boolean loadInDetail, Handler preDrawHandler) {
    Drawable foreground;
    Drawable background = null;
    boolean drawBackground =
            backgroundColor != Color.TRANSPARENT && backgroundColor != themeColor;

    if (foregroundDrawable instanceof Animatable) {
        // ...
    } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
        // ...
    } else { // 假设 splash screen icon 是一个静态图片
        // Adaptive icon don't handle transparency so we draw the background of the adaptive
        // icon with the same color as the window background color instead of using two layers
        foreground = new ImmobileIconDrawable(
                new AdaptiveForegroundDrawable(foregroundDrawable),
                srcIconSize, iconSize, loadInDetail, preDrawHandler);
    }

    if (drawBackground) {
        background = new MaskBackgroundDrawable(backgroundColor);
    }

    return new Drawable[]{foreground, background};
}

为 splash screen icon 创建 foreground drawable 非常有特色,在 splash screen worker thread 中把 splash screen icon drawable 绘制到 Bitmap 上,如下

// SplashscreenIconDrawableFactory.java
ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize, boolean loadInDetail,
        Handler preDrawHandler) {
    // loadInDetail 此时为 false
    if (loadInDetail) {
        
    } else {
        final float scale = (float) iconSize / srcIconSize;
        mMatrix.setScale(scale, scale);
        // preDrawHandler 是 splash screen worker thread 的 handler
        preDrawHandler.post(() -> preDrawIcon(drawable, srcIconSize));
    }
}

private void preDrawIcon(Drawable drawable, int size) {
    synchronized (mPaint) {
        // pre-draw icon 的 trace
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "preDrawIcon");
        
        // 把 splash screen icon drawable 提前绘制到 mIconBitmap
        mIconBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(mIconBitmap);
        drawable.setBounds(0, 0, size, size);
        drawable.draw(canvas);
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }
}

当前就是在 splash screen worker thread 中创建 SplashScreenView,而现在又在 splash screen worker thread 中 post runnable , 把 splash screen icon drawable 绘制到 Bitmap。

很有可能,在 SplashScreenView 创建完成后,执行 ViewRootImpl 的 traversal 的 draw 之时,Bitmap 可能还没有绘制完成,那么窗口的绘制,岂不是不完整的?不是这样的,foreground drawable 的 pre draw 和 draw 都使用了同步锁 mPaint ,如下

// SplashscreenIconDrawableFactory.java

private static class ImmobileIconDrawable extends Drawable implements Closeable {

    private void preDrawIcon(Drawable drawable, int size) {
        synchronized (mPaint) {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "preDrawIcon");
            mIconBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
            final Canvas canvas = new Canvas(mIconBitmap);
            drawable.setBounds(0, 0, size, size);
            drawable.draw(canvas);
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }

    @Override
    public void draw(Canvas canvas) {
        synchronized (mPaint) {
            if (mIconBitmap != null) {
                canvas.drawBitmap(mIconBitmap, mMatrix, mPaint);
            } else {
                // this shouldn't happen, but if it really happen, invalidate self to wait
                // for bitmap to be ready.
                invalidateSelf();
            }
        }
    }

因此,traversal 的 draw ,必须等到 Bitmap 绘制完成后,才能开始绘制窗口 View。

感想

启动窗口 View 的绘制,必须尽量快,因为它直接影响到 Transition 动画的延迟。系统采用多线程,把耗时任务,放到 worker thread 中执行,然后利用多线程同步,最终完成任务。这个设计,非常值得我们学习。