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都可以确认。