在第一阶段启动中,当窗口层级构建完成后,会利用 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 添加启动窗口的流程,非常简单,没什么好解释的,展示下流程图,如下
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 添加。但是,为了性能,采用了多线程来实现,具体流程如下
- 先在 splash screen worker thread 中创建启动窗口 View。
- 在 splash screen thread 中,通过 WindowManager 添加启动窗口的根 View。同时,ViewRootImpl 会向 Choreographer 注册一个类型为 CALLBACK_TRAVERSAL 的回调,用于执行 traversal,traversal 中会完成窗口的绘制。
- 在 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 中执行,然后利用多线程同步,最终完成任务。这个设计,非常值得我们学习。