SwallowJoe的博客

Be a real go-getter,
NEVER SETTLE!

0%

WMS(5)-启动窗口

以下分析基于Android S.

简述

前面几篇文章中,我们弄清楚了WMS中比较核心的几个类的作用以及初始化等流程。现在我们看看Activity启动时的启动窗口动画过程,以此为锲子剖析WMS相关流程。

启动窗口,如其名,最合理的地方应该是在Activity启动的时候播放其动画的,回到startActivityInner,开始看:

5-1

一. 启动窗口的初始化

1.1 ActivityStarter.startActivityInner

1
2
3
4
5
6
7
8
9
10
int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, Task inTask,
boolean restrictedBgActivity, NeededUriGrants intentGrants) {
......
// 交给对应Task启动Activity
mTargetRootTask.startActivityLocked(mStartActivity,
topRootTask != null ? topRootTask.getTopNonFinishingActivity() : null, newTask,
mKeepCurTransition, mOptions, startFromSamePackage);
......

在Activity启动过程中,在创建新的Task并将该ActivityRecord保存其中,之后就是将启动Activity的流程转交给Task继续执行.

1.2 Task.startActivityLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void startActivityLocked(ActivityRecord r, @Nullable ActivityRecord focusedTopActivity,
boolean newTask, boolean keepCurTransition, ActivityOptions options,
boolean samePackage) {
......
if ((!isHomeOrRecentsRootTask() || hasActivity()) && allowMoveToFront) {
......
boolean doShow = true;
......
// ActivityRecord.mLaunchTaskBehind 是用于表明启动该Activity时不需要动画
// 与options:"android:activity.animType"有关(调用了makeTaskLaunchBehind)
// 使用了这个标记说明正在启动的Activity将不会显示给用户,而是只能通过最近的任务列表使用
if (r.mLaunchTaskBehind) {
......
} else if (SHOW_APP_STARTING_PREVIEW && doShow) {
// 显示启动窗口
// 这里判断上一个task是否存在启动窗口
Task prevTask = r.getTask();
// 获取当前Task中的上一个具有启动窗口的ActivityRecord
ActivityRecord prev = prevTask.topActivityWithStartingWindow();
if (prev != null) {
// 当前Activity和启动窗口不在同一个task时,不会重用启动窗口
if (prev.getTask() != prevTask) {
prev = null;
}
// 当前Activity已经显示出来了,也不会重用启动窗口
else if (prev.nowVisible) {
prev = null;
}
}
final int splashScreenThemeResId = options != null
? options.getSplashScreenThemeResId() : 0;
// [1.3] 显示启动窗口, 我们这里是第一个Activity,不存在启动窗口, 所以prev为null
r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity),
splashScreenThemeResId, samePackage);
}
......
}

// topFocusedActivity是当前展示的前台具有焦点的Activity对应的ActivityRecord
// 这里是判断是否需要切换Task, 当然需要
private boolean isTaskSwitch(ActivityRecord r, ActivityRecord topFocusedActivity) {
return topFocusedActivity != null && r.getTask() != topFocusedActivity.getTask();
}

Task中执行Activity的启动主要是:

  1. 将该Activity对应的ActivityRecord放在该task合适的位置
  2. 顺便检查是否需要显示启动窗口,如果需要:
    1. 检查是否可以重用启动窗口:
      1. 当前Activity和启动窗口不在同一个task时,不会重用启动窗口
      2. 当前Activity已经显示出来了,也不会重用启动窗口
    2. 显示启动窗口

1.3 ActivityRecord.showStartingWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
int splashScreenTheme, boolean samePackage) {
if (mTaskOverlay) {
// 不显示overlay activity的启动窗口, 即始终处于最上层的Activity
return;
}
if (mPendingOptions != null
&& mPendingOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
// 当Activity动画类型为ANIM_SCENE_TRANSITION时,不显示启动窗口
return;
}

final CompatibilityInfo compatInfo =
mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo);
// 选择合适的窗口主题
final int resolvedTheme = evaluateStartingWindowTheme(packageName, theme,
splashScreenTheme);
// [1.4] 添加启动窗口
final boolean shown = addStartingWindow(packageName, resolvedTheme,
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
allowTaskSnapshot(),
mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal(),
samePackage);
// 成功添加时,标记此ActivityRecord的启动窗口状态为STARTING_WINDOW_SHOWN
if (shown) {
mStartingWindowState = STARTING_WINDOW_SHOWN;
}
}

显示启动窗口首先需要检查是否需要显示,以下两种情况不显示启动窗口:

  1. 当此Activity为始终处于最上层时
  2. Activity动画类型为ANIM_SCENE_TRANSITION时

然后选择合适窗口主题,添加启动窗口并在成功添加时,标记此ActivityRecord的启动窗口状态为STARTING_WINDOW_SHOWN,意为此Activity已经展示了启动窗口。

1.4 ActivityRecord.addStartingWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
boolean addStartingWindow(String pkg, int resolvedTheme, CompatibilityInfo compatInfo,
CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
boolean allowTaskSnapshot, boolean activityCreated, boolean samePackage) {
......
// 根据当前Activity相关参数配置启动窗口的窗口属性
final int typeParameter = mWmService.mStartingSurfaceController
.makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning,
allowTaskSnapshot, activityCreated, samePackage);
......
// 创建启动窗口相关数据
mStartingData = new SplashScreenStartingData(mWmService, pkg,
resolvedTheme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
getMergedOverrideConfiguration(), typeParameter);
// 转交给WMS的动画处理线程执行启动窗口的动画
scheduleAddStartingWindow();
return true;
}

void scheduleAddStartingWindow() {
// DEBUG_ENABLE_SHELL_DRAWER是"persist.debug.shell_starting_surface"
// 该属性默认为true
if (StartingSurfaceController.DEBUG_ENABLE_SHELL_DRAWER) {
// 执行启动窗口动画
mAddStartingWindow.run();
} else {
// 如果是使用postAtFrontOfQueue,会将该动画放在执行队列最前面执行
if (!mWmService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Enqueueing ADD_STARTING");
mWmService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
}
}
}

添加启动窗口的动作有很多,忽略细节,主要是封装该启动窗口的相关数据保存在SplashScreenStartingData中,然后执行启动窗口动画。

如果是转交给WMS的动画处理线程执行启动窗口的动画,而且是将该动画放在执行队列最前面执行。

至于WMS的mAnimationHandler是在类创建时初始化的:

1
2
3
4
5
6
7
// WMS.java
final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());

// AnimationThread.java
private AnimationThread() {
super("android.anim", THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
}

所以mAnimationHandler执行所在的线程就是”android.anim”线程。

该ActivityRecord中的mAddStartingWindow也是类初始化时创建的:

1
2
3
4
5
6
// ActivityRecord.java
private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();

private class AddStartingWindow implements Runnable {
......
}

二. 启动窗口的动画

2.1 AddStartingWindow.run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Override
public void run() {
final StartingData startingData;
synchronized (mWmService.mGlobalLock) {
......
if (mStartingData == null) {
// 该对象被置null说明启动窗口动画被取消了...
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"startingData was nulled out before handling"
+ " mAddStartingWindow: %s", ActivityRecord.this);
return;
}
startingData = mStartingData;
}

ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s",
this, startingData);

WindowManagerPolicy.StartingSurface surface = null;
try {
// [2.2] 创建启动窗口Surface
surface = startingData.createStartingSurface(ActivityRecord.this);
} catch (Exception e) {
Slog.w(TAG, "Exception when adding starting window", e);
}
if (surface != null) {
boolean abort = false;
synchronized (mWmService.mGlobalLock) {
// 如果mStartingData现在被置null了,说明窗口成功创建并被添加了
if (mStartingData == null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Aborted starting %s: startingData=%s",
ActivityRecord.this, mStartingData);

mStartingWindow = null;
mStartingData = null;
abort = true;
} else {
// 将创建的启动窗口surface赋值给mStartingSurface
mStartingSurface = surface;
}
......
}
if (abort) {
// 如果启动窗口动画中止,则移除该Surface
surface.remove(false /* prepareAnimation */);
}
......
}

AddStartingWindow就是一个runnable, 执行的时候先判断当前启动窗口是否仍需要,如果需要就创建启动窗口的Surface,如果启动窗口被中止,则移除创建的Surface.

2.2 StartingData.createStartingSurface

ActivityRecord中的mStartingData就是SplashScreenStartingData类型的对象。SplashScreenStartingData是StartingData的子类。

1
2
3
4
5
6
7
@Override
StartingSurface createStartingSurface(ActivityRecord activity) {
return mService.mStartingSurfaceController.createSplashScreenStartingSurface(
activity, mPkg, mTheme, mCompatInfo, mNonLocalizedLabel, mLabelRes, mIcon,
mLogo, mWindowFlags, mMergedOverrideConfiguration,
activity.getDisplayContent().getDisplayId());
}

通过WMS中的mStartingSurfaceController来创建StartingSurface, mStartingSurfaceController是在WMS初始化的时候创建,用来管理创建和释放启动窗口Surface。

2.3 StartingSurfaceController.createSplashScreenStartingSurface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, String packageName,
int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
......

synchronized (mService.mGlobalLock) {
// 这里的task是ActivityRecord中新创建的task
final Task task = activity.getTask();
// 注意activity.token其实就是ActivityRecord.Token, 在ActivityRecord初始化时有分析,作为该ActivityRecord本身
// [2.4] mTaskOrganizerController.addStartingWindow
if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(
task, activity.token, theme, null /* taskSnapshot */)) {
// 最终创建的Surface是ShellStartingSurface.
return new ShellStartingSurface(task);
}
}
return null;
}

最终创建的Surface是ShellStartingSurface.

2.4 TaskOrganizerController.addStartingWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
boolean addStartingWindow(Task task, IBinder appToken, int launchTheme,
TaskSnapshot taskSnapshot) {
final Task rootTask = task.getRootTask();
if (rootTask == null || rootTask.mTaskOrganizer == null) {
return false;
}
final TaskOrganizerState state =
mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
// 通过TaskOrganizerState执行添加动作
state.addStartingWindow(task, appToken, launchTheme, taskSnapshot);
return true;
}

// TaskOrganizerState
private final TaskOrganizerCallbacks mOrganizer;
void addStartingWindow(Task t, IBinder appToken, int launchTheme,
TaskSnapshot taskSnapshot) {
mOrganizer.addStartingWindow(t, appToken, launchTheme, taskSnapshot);
}

// TaskOrganizerCallbacks
final ITaskOrganizer mTaskOrganizer;
void addStartingWindow(Task task, IBinder appToken, int launchTheme,
TaskSnapshot taskSnapshot) {
final StartingWindowInfo info = task.getStartingWindowInfo();
if (launchTheme != 0) {
info.splashScreenThemeResId = launchTheme;
}
info.mTaskSnapshot = taskSnapshot;
try {
// 交给ITaskOrganizer执行,appToken就是ActivityRecord.token, 即ActivityRecord.Token类的对象
mTaskOrganizer.addStartingWindow(info, appToken);
} catch (RemoteException e) {
Slog.e(TAG, "Exception sending onTaskStart callback", e);
}
}

这里有点绕,我们看看mTaskOrganizer是什么,看看TaskOrganizerCallbacks和TaskOrganizerState的初始化可以知道:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TaskOrganizerCallbacks(ITaskOrganizer taskOrg,
Consumer<Runnable> deferTaskOrgCallbacksConsumer) {
mDeferTaskOrgCallbacksConsumer = deferTaskOrgCallbacksConsumer;
// mTaskOrganizer是该类对象初始化时传入的ITaskOrganizer
mTaskOrganizer = taskOrg;
}

TaskOrganizerState(ITaskOrganizer organizer, int uid) {
final Consumer<Runnable> deferTaskOrgCallbacksConsumer =
mDeferTaskOrgCallbacksConsumer != null
? mDeferTaskOrgCallbacksConsumer
: mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
mOrganizer = new TaskOrganizerCallbacks(organizer, deferTaskOrgCallbacksConsumer);
mDeathRecipient = new DeathRecipient(organizer);
try {
organizer.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
Slog.e(TAG, "TaskOrganizer failed to register death recipient");
}
mUid = uid;
}

所以mTaskOrganizer还是TaskOrganizerState初始化时传入的。那么TaskOrganizerState是怎么初始化的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// TaskOrganizerController.java#121
@Override
public ParceledListSlice<TaskAppearedInfo> registerTaskOrganizer(ITaskOrganizer organizer) {
......
try {
synchronized (mGlobalLock) {
......
if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) {
mTaskOrganizers.add(organizer);
mTaskOrganizerStates.put(organizer.asBinder(),
new TaskOrganizerState(organizer, uid));
}
.......
......
......
}

所以是有其他进程调用了TaskOrganizerController的registerTaskOrganizer将该成员注册了,而这里的方法仅有TaskOrganizer.java#72调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
@CallSuper
@NonNull
public List<TaskAppearedInfo> registerOrganizer() {
try {
return mTaskOrganizerController.registerTaskOrganizer(mInterface).getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() {
@Override
public void addStartingWindow(StartingWindowInfo windowInfo,
IBinder appToken) {
mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo, appToken));
}
......
}

@BinderThread
public void addStartingWindow(@NonNull StartingWindowInfo info,
@NonNull IBinder appToken) {}

所以这个是将启动窗口的信息通过binder调用,添加到实现TaskOrganizer的addStartingWindow方法的其他进程了,并非是系统进程管控。

总的来说AddStartingWindow.run这个就是做了两件事:

  1. 创建启动窗口Surface, 即ShellStartingSurface
  2. 将启动窗口信息通过binder调用传递给实现TaskOrganizer的进程

这里的流程和R的差异有点大,推测是Google希望启动窗口可以交给Launcher或者SystemUI实现,而不是由framework的system_server进程来管控。

搜索SystemUI源码可以发现,继承TaskOrganizer的类有一个:ShellTaskOrganizer!

2.5 ShellTaskOrganizer.addStartingWindow

1
2
3
4
5
6
@Override
public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
if (mStartingWindow != null) {
mStartingWindow.addStartingWindow(info, appToken);
}
}

看来Google还真是将启动窗口交给SystemUI模块了。

三. SystemUI负责的启动窗口

上面我们知道一个Activity启动后,从App进程交给SystemServer处理,在创建该Activity的ActivityRecord, 并将其保存在对应的Task之后,会在需要的时候开始启动窗口的播放。

启动窗口在Android S上是交给SystemUI进程负责了,在以前是交给SystemServer的“android.anim”线程处理。

关于R上SystemUI的ShellTaskOrganizer这一块涉及到Dagger2, 是一款基于 Java 注解来实现的在编译阶段完成依赖注入的开源库的使用,这里先不展开讲了,感兴趣的可以自行研究。

ShellTaskOrganizer中的mStartingWindow是StartingWindowController类对象。

3.1 StartingWindowController.addStartingWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
mSplashScreenExecutor.execute(() -> {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");

final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType(
windowInfo);
final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
if (mTaskLaunchingCallback != null && shouldSendToListener(suggestionType)) {
mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType);
}
if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
false /* emptyView */);
} else if (suggestionType == STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN) {
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
true /* emptyView */);
} else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
final TaskSnapshot snapshot = windowInfo.mTaskSnapshot;
mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken,
snapshot);
} else /* suggestionType == STARTING_WINDOW_TYPE_NONE */ {
// Don't add a staring window.
}

Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
});
}

根据启动窗口类型选择不同的启动窗口,这里我们假设是STARTING_WINDOW_TYPE_SPLASH_SCREEN类型。

3.2 StartingSurfaceDrawer.addSplashScreenStartingWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
boolean emptyView) {
final RunningTaskInfo taskInfo = windowInfo.taskInfo;
final ActivityInfo activityInfo = taskInfo.topActivityInfo;
......
Context context = mContext;
......
// 获取启动窗口显示view的资源id
final int[] splashscreenContentResId = new int[1];
// 这里的R是 "import com.android.internal.R;" 说明是framework/res中的资源
getWindowResFromContext(context, a -> {
splashscreenContentResId[0] =
a.getResourceId(R.styleable.Window_windowSplashscreenContent, 0);
showWallpaper[0] = a.getBoolean(R.styleable.Window_windowShowWallpaper, false);
});
......
final PhoneWindow win = new PhoneWindow(context);
win.setIsStartingWindow(true);
......
win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
......
win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);

final WindowManager.LayoutParams params = win.getAttributes();
// 这里的appToken就是一路传递过来的待启动的activity的appToken,即ActivityRecord.Token类的对象
params.token = appToken;
params.packageName = activityInfo.packageName;
......
params.setTitle("Splash Screen " + activityInfo.packageName);
final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
final Runnable setViewSynchronized = () -> {
// 获取启动窗口的ContentView
// SplashScreenViewSupplier.get必须要等待其被调用setView之后才会返回,否则会阻塞等待
SplashScreenView contentView = viewSupplier.get();
......
// 设置启动窗口View内容
win.setContentView(contentView);
contentView.cacheRootWindow(win);
......
}
// [3.3] 创建启动窗口对应的View
mSplashscreenContentDrawer.createContentView(context, emptyView,
splashscreenContentResId[0], activityInfo, taskId, viewSupplier::setView);
......
final View view = win.getDecorView();
final WindowManager wm = mContext.getSystemService(WindowManager.class);
// 将启动窗口的View添加进WMS,创建对应的WindowState
postAddWindow(taskId, appToken, view, wm, params);
// 请求Vsync,下一帧时调用setViewSynchronized,设置启动窗口View内容
mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
}

protected void postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
WindowManager.LayoutParams params) {
......
// WMS用来创建启动窗口对应的WindowState
// 注意LayoutParams中的token是待启动的activity的appToken,即ActivityRecord.Token类的对象
wm.addView(view, params);
......
}

这里创建启动窗口对应的PhoneWindow以及设置相关的参数,并将启动窗口的DecorView和窗口属性通过binder传给WMS用来创建对应的WindowState,最后通过Choreographer请求vsync,在下一帧的时候设置该启动窗口的内容。

3.3 SplashscreenContentDrawer.createContentView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void createContentView(Context context, boolean emptyView, int splashScreenResId,
ActivityInfo info, int taskId, Consumer<SplashScreenView> consumer) {
mSplashscreenWorkerHandler.post(() -> {
SplashScreenView contentView;
try {
// [3.3.1] 制作启动窗口显示的view
contentView = SplashscreenContentDrawer.makeSplashscreenContent(
context, splashScreenResId);
// Android S之后这个一般都是null的
if (contentView == null) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "makeSplashScreenContentView");
// emptyView是false的
if (emptyView) {
contentView = makeEmptySplashScreenContentView(context);
} else {
// [3.3.2] 最后启动窗口的contentView是在这里创建的
contentView = makeSplashScreenContentView(context, info);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
} catch (RuntimeException e) {
Slog.w(TAG, "failed creating starting window content at taskId: "
+ taskId, e);
contentView = null;
}
// [3.4] consumer是传入的viewSupplier::setView
consumer.accept(contentView);
});
}

这里是创建启动窗口显示的内容,首先通过SplashscreenContentDrawer创建SplashScreenView.

3.3.1 SplashscreenContentDrawer.makeSplashscreenContent

1
2
3
4
5
6
7
8
9
10
private static SplashScreenView makeSplashscreenContent(Context ctx,
int splashscreenContentResId) {
// 获取当前systemui的sdk版本
final int targetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
// Andriod S之后不支持Window_SplashscreenContent了
if (targetSdkVersion >= Build.VERSION_CODES.S) {
return null;
}
......
}

Andriod S之后不支持Window_windowSplashscreenContent了.

3.3.2 SplashscreenContentDrawer.makeSplashScreenContentView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai) {
// 重置View相关配置
updateDensity();
// 重置窗口相关属性至mTmpAttrs中保存
getWindowAttrs(context, mTmpAttrs);
final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
final int animationDuration;
Drawable iconDrawable;
// mReplaceIcon默认是R.styleable.Window_windowSplashScreenAnimatedIcon
if (mTmpAttrs.mReplaceIcon != null) {
iconDrawable = mTmpAttrs.mReplaceIcon;
animationDuration = Math.max(0,
Math.min(mTmpAttrs.mAnimationDuration, mMaxAnimatableIconDuration));
} else {
iconDrawable = mIconProvider.getIconForUI(
ai, getUserHandleForUid(ai.applicationInfo.uid));
if (iconDrawable == null) {
iconDrawable = context.getPackageManager().getDefaultActivityIcon();
}
animationDuration = 0;
}
// 选择启动窗口背景颜色
final int themeBGColor = peekWindowBGColor(context);
// [3.3.3] 创建SplashScreenView
return builder
.setContext(context)
.setWindowBGColor(themeBGColor)
.setIconDrawable(iconDrawable)
.setIconAnimationDuration(animationDuration)
.setBrandingDrawable(mTmpAttrs.mBrandingImage)
.setIconBackground(mTmpAttrs.mIconBgColor).build();
}

这里首先重置相关配置属性,然后根据默认配置创建SplashScreenView。

3.3.3 StartingWindowViewBuilder.build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SplashScreenView build() {
......
// mIconDrawable必须不为空且是AdaptiveIconDrawable的实例
if (!processAdaptiveIcon() && mIconDrawable != null) {
if (DEBUG) {
Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
}
createIconDrawable(mIconDrawable, mIconSize);
}
final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
// 填充SplashScreenView内容
mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable);
mBuildComplete = true;
return mCachedResult;
}

这里也仅仅是检查了下mIconDrawable是否为AdaptiveIconDrawable的实例,如果不是则重新创建一个。然后使用fillViewWithIcon填充内容,创建SplashScreenView.

3.3.4 StartingWindowViewBuilder.fillViewWithIcon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private SplashScreenView fillViewWithIcon(Context context,
int iconSize, Drawable iconDrawable) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "fillViewWithIcon");
final SplashScreenView.Builder builder = new SplashScreenView.Builder(context);
builder.setIconSize(iconSize).setBackgroundColor(mThemeColor)
.setIconBackground(mIconBackground);
if (iconDrawable != null) {
builder.setCenterViewDrawable(iconDrawable);
}
builder.setAnimationDurationMillis(mIconAnimationDuration);
if (mBrandingDrawable != null) {
builder.setBrandingDrawable(mBrandingDrawable, mBrandingImageWidth,
mBrandingImageHeight);
}
// 3.3.5 实例化SplashScreenView
final SplashScreenView splashScreenView = builder.build();
if (DEBUG) {
Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
}
splashScreenView.makeSystemUIColorsTransparent();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return splashScreenView;
}

设置待创建的SplashScreenView相关的内容,如主题颜色、图标背景色、图标动画时长、三方应用的图标等等,然后通过build构建SplashScreenView.

3.3.5 SplashScreenView.Builder.build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public SplashScreenView build() {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#build");
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
final SplashScreenView view = (SplashScreenView)
layoutInflater.inflate(R.layout.splash_screen_view, null, false);
......
view.mIconView = view.findViewById(R.id.splashscreen_icon_view);
view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view);
......
// 设置icon图像
if (mIconDrawable != null) {
view.mIconView.setBackground(mIconDrawable);
view.initIconAnimation(mIconDrawable,
mIconAnimationDuration != null ? mIconAnimationDuration.toMillis() : 0);
}
......
// 设置brand图像
if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0 && mBrandingDrawable != null) {
......
view.mBrandingImageView.setBackground(mBrandingDrawable);
}
......
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return view;
}

SplashScreenView是继承FrameLayout的,通过LayoutInflater实例化splash_screen_view作为其内容布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<android.window.SplashScreenView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical">

<View android:id="@+id/splashscreen_icon_view"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:contentDescription="@string/splash_screen_view_icon_description"/>

<View android:id="@+id/splashscreen_branding_view"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="60dp"
android:contentDescription="@string/splash_screen_view_branding_description"/>

</android.window.SplashScreenView>

这个非常简单,就是两个View, 所以说启动窗口的显示内容就是两个图标(如果有图标对应的image不为null,则会显示)。

3.4 SplashScreenViewSupplier.setView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
private SplashScreenView mView;
private boolean mIsViewSet;
void setView(SplashScreenView view) {
synchronized (this) {
// 保存SplashScreenView并通知其他线程继续执行get
mView = view;
mIsViewSet = true;
notify();
}
}

@Override
public @Nullable SplashScreenView get() {
synchronized (this) {
while (!mIsViewSet) {
try {
wait();
} catch (InterruptedException ignored) {
}
}
return mView;
}
}
}

这个类的主要作用就是将SplashScreenView的创建交给与使用线程不同的线程。

四.小结

来一张流程图回顾下启动窗口被添加的过程:

5-2

虽然启动窗口对应的View和内容是通过SystemUI创建的,但是其还是被WMS归属为待启动的Activity的Task中的一员。究其原因还是其窗口属性中的token是该Activity对应的ActivityRecord.Token。SystemUI创建启动窗口的内容后,会在下一帧来时将该View添加到WMS中,然后WMS就会在addWindow里创建对应的WindowState。

欢迎关注我的其它发布渠道