StaringWindow的创建与移除 - finalxiaoxiao/study GitHub Wiki
-
本文主要研究内容:
-
StaringWindow是什么
-
StartingWindow的框架
-
SplashScreen类型StartingWindow的创建和销毁
-
SnapShot类型StartingWindow的创建和销毁
-
TaskSnapshot的创建与销毁
-
-
本文研究基于的版本:
android-11.0.0_r3
在Activity真正显示之前,可能需要处理大量的耗时任务,像进程创建,资源加载,窗口绘制等等。所以在窗口的过渡动画完成之后,可能应用还没有完成页面的绘制,我们需要一个页面来等待真正的Activity显示。又或者说窗口的过渡动画使用什么素材呢?
StaringWindow的存在就是为了解决这样的问题,它所扮演的角色便是 应用启动 时的窗口代替者。
和Activity的窗口一样,StaringWindow也是由WindowState和Surface构成的。它使用的窗口类型是TYPE_APPLICATION_STARTING。在不同的场景,有不同类型的StaringWindow:
-
STARTING_WINDOW_TYPE_NONE
不启动StaringWindow,常见于应用内的Activity切换。 -
STARTING_WINDOW_TYPE_SNAPSHOT
快照启动窗口,显示的内容为最近一次的可见内容的快照。使用场景如task从后台到前台的切换,屏幕解锁。 -
STARTING_WINDOW_TYPE_SPLASH_SCREEN
闪屏启动窗口,显示的内容是空白的窗口,背景和应用的主题有关。使用场景如应用的冷启动。
-
ActivityStack
存放task的ActivityRecord的容器,也是处理ActivityRecord生命周期的主要参与者。starting window的启动流程起点就是ActivityStack.startActivityLocked()。 -
ActivityRecord
系统进程中的Activity,也是窗口容器。Activity窗口和启动窗口都是它的child。因此 启动窗口的添加和移除都是由ActivityRecord负责的。 -
WindowState
系统进程中的窗口,在窗口管理系统中是控制页面大小位置等属性的基本单元。starting window启动中会创建一个TYPE_APPLICATION_STARTING窗口类型的WindowState。 -
StartingData
抽象了starting window的数据模型,负责构造 StartingSurface。-
SplashScreenStartingData
闪屏类型启动窗口的StartingData实现,封装了闪屏类型启动窗口所需要的数据如theme,icon,windowflags等等,这些数据来源于ActivityRecord。 -
SnapshotStartingData
快照类型启动窗口的StartingData实现,持有了TaskSnapshot。
-
-
StartingSurface
用于封装starting window的内容。使用remove()移除starting window。-
SplashScreenSurface
持有一个View,remove()使用WindowManager.removeView()。 -
TaskSnapshotSurface
由于不使用View直接使用应用的截图缓存绘制窗口,它直接向WMS申请WindowState和布局,在Surface上绘制内容。remove()也是直接使用IWindowSession.remove()。这样一看它的职能和ViewRootImpl类似,是一个没有View的简易ViewRootImpl。
-


-
StaringWindow的创建时机在Activity的启动时。在前面提到Activity的启动是需要一个空白页面或者截图页面过渡的。所以在系统进程收到Activity的启动请求时,根据不同的场景分配不同的启动窗口类型,绘制启动窗口。
-
StaringWindow的移除时机在Activity的绘制完成之后,当Activity完成绘制之后,StartingWindow的使命也就结束了,所以将它移除。

从时序图中,我们可以看出StaringWindow的创建与移除主要通过StartingSurface的create和remove实现的。
在Launcher启动App场景下,StartingWindow的启动和移除入口是ActivityStack.startActivityLocked()和WindowManagerService.finishDrawing()。
Tip
|
在Recents启动App、屏幕解锁的场景下,StaringWindow的add与remove会走其他的通道, 但是内部实现是一致的。 |
当应用冷启动的时候,会添加SplashScreen类型的StartingWindow。让我们通过代码梳理SplashScreen类型的StaringWindow的添加与移除流程。
class ActivityStack extends Task {
...
void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
boolean newTask, boolean keepCurTransition, ActivityOptions options) {
...
boolean doShow = true; // (1)
...
if (r.mLaunchTaskBehind) {
} else if (SHOW_APP_STARTING_PREVIEW && doShow) {
...
r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));
}
...
}
...
}
-
doShow指是否执行ActivityRecord.showStartingWindow方法,在某些场景下会被置为false
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
...
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
...
final boolean shown = addStartingWindow(packageName, theme,
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
allowTaskSnapshot(),
mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal()); // (1)
if (shown) {
mStartingWindowState = STARTING_WINDOW_SHOWN; // (2)
}
...
}
boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
boolean allowTaskSnapshot, boolean activityCreated) {
...
final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
allowTaskSnapshot, activityCreated, snapshot); // (3)
if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
...
return createSnapshot(snapshot);
}
...
if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
return false;
}
mStartingData = new SplashScreenStartingData(mWmService, pkg,
theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
getMergedOverrideConfiguration());
scheduleAddStartingWindow(); // (4)
}
...
}
-
可以发现ActivityRecord把theme,campatInfor, icon等等这些Activity相关的属性传入addStartingWindow中,这其实是为了让StartingWindow在显示上尽量和实际显示的Activity相似。
-
StartingWindow的状态有三种,NOT_SHOWN→SHOWN→REMOVED。知道它的显示状态有什么作用呢?在Activity的启动过程中,如果发现有其他的ActivityRecord的mStartingWindowState还处于SHOWN,那就说明它的remove流程出现了问题,是个orphaned starting window孤儿启动窗口,那么我们就可以把它remove回收了。
-
type,启动窗口类型,它决定了接下来的程序走向,snapshot类型的启动窗口将会在之后详细展开。
-
在保存SplashScreen的StartingData之后,异步添加启动窗口,这是为了不让启动窗口的绘制阻塞Activity的启动。
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
...
private int getStartingWindowType(boolean newTask, boolean taskSwitch,
boolean processRunning, boolean allowTaskSnapshot,
boolean activityCreated, ActivityManager.TaskSnapshot snapshot) {
if (newTask || !processRunning || (taskSwitch && !activityCreated)) { // (1)
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) { // (2)
if (isSnapshotCompatible(snapshot)) {
return STARTING_WINDOW_TYPE_SNAPSHOT;
}
if (!isActivityTypeHome()) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
}
return STARTING_WINDOW_TYPE_NONE;
} else {
return STARTING_WINDOW_TYPE_NONE;
}
}
...
}
-
newTask、进程未启动、切换task且新创建Activity时,获得的type为splash screen,否则进入第二步。(启动模式使用newTask、冷启动、新启动一个Activity并且切换task都会使用splash screen)
-
切换task且允许snapshot时,尝试获取STARTING_WINDOW_TYPE_SNAPSHOT(isSnapshotCompatible是指Activity的屏幕方向和截图的屏幕方向是否一致,如果不一致那自然不能使用截图作为启动窗口)。若不满足条件进入第三步
-
不创建StartingWindow(这种场景常见于应用内的Activity切换)。
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
...
void scheduleAddStartingWindow() {
if (!mWmService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Enqueueing ADD_STARTING");
mWmService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
}
}
private class AddStartingWindow implements Runnable {
@Override
public void run() {
// Can be accessed without holding the global lock
...
WindowManagerPolicy.StartingSurface surface = null;
try {
surface = startingData.createStartingSurface(ActivityRecord.this);
} catch (Exception e) {
Slog.w(TAG, "Exception when adding starting window", e);
}
...
}
}
}
class SplashScreenStartingData extends StartingData {
...
@Override
StartingSurface createStartingSurface(ActivityRecord activity) {
return mService.mPolicy.addSplashScreen(activity.token, mPkg, mTheme, mCompatInfo,
mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
mMergedOverrideConfiguration, activity.getDisplayContent().getDisplayId());
}
...
}
把添加Splash Screen类型的启动窗口的任务交给了PhoneWindowManager
public class PhoneWindowManager implements WindowManagerPolicy {
...
@Override
public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
int logo, int windowFlags, Configuration overrideConfig, int displayId) {
...
WindowManager wm = null;
View view = null;
...
final PhoneWindow win = new PhoneWindow(context); // (1)
win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
...
win.setFlags(
windowFlags|
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
windowFlags|
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); // (2)
win.setDefaultIcon(icon);
win.setDefaultLogo(logo);
final WindowManager.LayoutParams params = win.getAttributes();
params.token = appToken; // (3)
params.packageName = packageName;
params.windowAnimations = win.getWindowStyle().getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
params.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
if (!compatInfo.supportsScreen()) {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
}
params.setTitle("Splash Screen " + packageName);
addSplashscreenContent(win, context);
wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
view = win.getDecorView();
wm.addView(view, params); // (4)
return view.getParent() != null ? new SplashScreenSurface(view, appToken) : null;
...
}
...
}
-
我们get了除Activity、Dialog之外,PhoneWindow的又一个用法。作为Activity的代替者,StartingWindow的绘制和Activity的绘制十分的类似。
-
StaringWindow只是暂时代替Activity显示,所以无需处理Input事件的,在这里设置Flag禁用touch和Focus。
-
和Activity使用同样的appToken,建立StaringWindow与ActivityRecord之间的桥梁。
-
使用addView添加启动窗口。ActivityRecord会保存StartingSurface对象,在Activity绘制完成时,通过这个StartingSurface移除启动窗口。
回顾之前提到的StaringWindow的添加与移除主流程,在Activity绘制完成之后系统会触发StartingWindow的移除机制。那么我们接着从WindowManagerService.finishDrawingWindow()开始分析。
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
...
void finishDrawingWindow(Session session, IWindow client,
@Nullable SurfaceControl.Transaction postDrawTransaction) {
...
WindowState win = windowForClientLocked(session, client, false);
if (win != null && win.finishDrawing(postDrawTransaction)) {
...
mWindowPlacerLocked.requestTraversal();
}
...
}
...
}
WindowManagerService.finishDrawingWindow()是窗口绘制流程中的内容,这里就不详细展开。直接进入其中的WindowState.performShowLocked()方法中分析。
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
InsetsControlTarget {
...
boolean performShowLocked() {
...
if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
if (mAttrs.type != TYPE_APPLICATION_STARTING) {
mActivityRecord.onFirstWindowDrawn(this, mWinAnimator); // (1)
}
...
}
...
}
...
}
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
...
void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
...
removeStartingWindow();
...
}
void removeStartingWindow() {
...
final WindowManagerPolicy.StartingSurface surface;
if (mStartingData != null) {
surface = startingSurface;
mStartingData = null;
startingSurface = null;
startingWindow = null;
startingDisplayed = false;
if (surface == null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"startingWindow was set but startingSurface==null, couldn't "
+ "remove");
return;
}
} // (2)
...
mWmService.mAnimationHandler.post(() -> {
try {
surface.remove();
} catch (Exception e) {
Slog.w(TAG_WM, "Exception when removing starting window", e);
}
}); // (3)
}
...
}
-
当窗口类型不是TYPE_APPLICATION_STARTING,这表明真正的Activity的窗口已经绘制完成了,正式进入StaringWindow的移除流程。
-
重置staringWindow的状态值,如果surface为空说明StartingWindow已经在其他的地方被移除了。
-
surface的remove流程也就是窗口的remove流程,耗时较长需要异步执行。
class SplashScreenSurface implements StartingSurface {
@Override
public void remove() {
...
final WindowManager wm = mView.getContext().getSystemService(WindowManager.class);
wm.removeView(mView);
}
}
SplashScreenSurface的remove和Activity的窗口移除流程类似,也是通过WindowManager.remove完成。其中流程属于窗口的销毁流程,主要内容就是回收窗口添加时创建的数据,如ViewRootImpl,WindowState等等,这里就不详细展开了。
当ActivityRecord.getStartingWindowType()获取到的类型为STARTING_WINDOW_TYPE_SNAPSHOT时,会添加SNAPSHOT类型的StartingWindow。之前已经分析了SplashScreen类型的启动窗口流程,所以我们接下来只需要对比分析SnapShot的它们之间的差异部分。
启动窗口的主流程都是一致的,只是窗口中填充的内容不一样,所以我们需要分析StartingData和StartingSurface的差异。
class SnapshotStartingData extends StartingData {
...
@Override
StartingSurface createStartingSurface(ActivityRecord activity) {
return mService.mTaskSnapshotController.createStartingSurface(activity, mSnapshot);
}
}
class TaskSnapshotController {
...
StartingSurface createStartingSurface(ActivityRecord activity,
TaskSnapshot snapshot) {
return TaskSnapshotSurface.create(mService, activity, snapshot);
}
}
class TaskSnapshotSurface implements StartingSurface {
...
static TaskSnapshotSurface create(WindowManagerService service, ActivityRecord activity,
TaskSnapshot snapshot) {
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
final Window window = new Window();
final IWindowSession session = WindowManagerGlobal.getWindowSession();
window.setSession(session);
final SurfaceControl surfaceControl = new SurfaceControl();
...
}
try {
final int res = session.addToDisplay(window, window.mSeq, layoutParams,
View.GONE, activity.getDisplayContent().getDisplayId(), tmpFrame, tmpRect,
tmpRect, tmpCutout, null, mTmpInsetsState, mTempControls); // (1)
...
} catch (RemoteException e) {
// Local call.
}
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis,
windowFlags, windowPrivateFlags, taskBounds, currentOrientation, insetsState); // (2)
window.setOuter(snapshotSurface);
try {
session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1,
tmpFrame, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect,
tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState,
mTempControls, sTmpSurfaceSize, sTmpSurfaceControl); // (3)
} catch (RemoteException e) {
// Local call.
}
...
snapshotSurface.drawSnapshot(); // (4)
return snapshotSurface;
}
}
-
在准备好了LayoutParams,IWindow等等添加窗口所需要的数据,向WMS申请添加WindowState。
-
将绘制相关的参数保存到TaskSnapshotSurface,为之后绘制页面做准备。
-
向WMS申请布局,在这里完成了Surface的创建。
-
绘制SnapShot中的内容,主要步骤是将TaskSnapShot中的绘图buffer应用到Surface上。
class TaskSnapshotSurface implements StartingSurface {
...
private void drawSnapshot() {
...
drawSizeMatchSnapshot();
...
reportDrawn();
...
}
...
private void drawSizeMatchSnapshot() {
mSurface.attachAndQueueBufferWithColorSpace(mSnapshot.getSnapshot(),
mSnapshot.getColorSpace()); // (1)
mSurface.release();
}
...
private void reportDrawn() {
try {
mSession.finishDrawing(mWindow, null /* postDrawTransaction */); // (2)
} catch (RemoteException e) {
// Local call.
}
}
...
}
-
将之前截图的GraphicBuffer设置到Surface上
-
通知WMS绘制完成,WMS会将窗口的绘制状态更新并将Surface设置为可见。
同样只分析remove流程的差异部分
class TaskSnapshotSurface implements StartingSurface {
...
@Override
public void remove() {
...
mSession.remove(mWindow); // (1)
...
}
...
}
-
通知WMS移除WindowState和Surface。WindowManager.removeView()也会使用到IWindowSession.remove(),还会对ViewRootImpl进行回收。
SnapShot类型的StaringWindow的绘制流程中使用的TaskSnapshot是如何获取的呢?我们接下来分析它的创建与移除流程。
-
WindowManagerService
窗口管理服务,TaskSnapshotController是它的成员之一。 -
TaskSnapshotController
是TaskSnapShot对外的接口,管理TaskSnapshot的创建,获取与销毁。 -
TaskSnapshotLoader
从磁盘中加载TaskSnapshot,TaskSnapShot不仅会缓存在内存里,还会写在磁盘中。TaskSnapshotLoader负责从磁盘中恢复应用快照。(示例场景:启动数个应用→重启系统→进入多任务。在这种场景下,系统重启之后,多任务的显示需要页面快照,系统进程此时没有TaskSnapShot的缓存,这个时候便会从磁盘中加载快照。) -
TaskSnapshotPersister
负责TaskSnapshot在磁盘中的创建,读取与销毁。 -
TaskSnapshotCache
负责TaskSnapshot在内存中的创建,读取与销毁。当应用从前台退到后台时,TaskSnapShot就会被缓存在TaskSnapshotCache中。并且ActivityRecord与TaskId存在映射,TaskId与TaskSnapShot存在映射。这样一来,在应用从后台回到前台时,通过taskId便能获取TaskSnapShot;在应用退出时,能通过ActivityRecord获取TaskSnapShot,将TaskSnapShot移除。-
TaskSnapshot
应用的快照,存储着应用快照的数据,如Task的Id、Size、TopActivity的ComponentName和最关键的GraphicBuffer。TaskSnapshot实现了Parcelable,因为其他应用也需要应用快照例如Launcher的多任务。
-
image::https://user-images.githubusercontent.com/49864524/209103993-6fc2163e-980f-4ec2-aa01-b98e08e7e892.png
image::https://user-images.githubusercontent.com/49864524/209104009-397d0a80-78d3-4a49-a121-09518fd58890.png
如图是比较常见的TaskSnapShot的创建与移除入口。
-
应用切换至后台。因为应用从后台切换到前台需要使用快照填充启动窗口,所以需要在它进入后台时保存快照。应用的切场会填充过渡动画,task的快照流程的起点被设计在过渡动画的起点。
-
Back键退出应用。使用back键退出应用时Activity会被销毁,因此再次启动应用时不属于热启动,不需要应用快照。但是多任务页面仍需要应用快照,所以只移除应用快照的缓存。
-
从多任务退出应用。从多任务页面中移除应用时进程会被销毁,因此再次启动应用属于冷启动。此时没有任何场景需要再使用应用快照,所以将它的缓存和磁盘存储全部移除。
image::https://user-images.githubusercontent.com/49864524/209104056-b0b84d32-f433-4300-8756-f85a515f05bb.png
-
当过渡动画开始执行时,过渡动画控制类AppTransitionController通知snapshot控制类TaskSnapshotController,过渡动画开始执行。那么TaskSnapshotController会将正在关闭的应用的快照存储到缓存和磁盘。
-
activity执行完destroy流程,将状态回传给ATMS。这一状态被TaskSnapshotController所关注,如果destroy的Activity与缓存中的snapshot有映射,那么这个snapshot的缓存会被移除。
-
IPC调用的removeTask总是会将task从多任务中移除,而TaskSnapshotController关心多任务中的应用状态。当task在多任务中被移除时,这个task所对应的snapshot缓存和持久化存储都会被移除。
TaskSnapShot的创建在过渡动画开始时执行,过渡动画的创建执行流程不在本文讨论范围之内,我们只需要知道过渡动画的大致流程为:准备过渡动画→执行过渡动画。
-
当应用启动时和应用退到后台时,系统进程都会为应用准备过渡动画。
-
执行过渡动画的时机位于WMS的布局流程中,只有在窗口完成绘制之后,才能执行过渡动画。
我们以WMS的布局流程为起点为入口,分析TaskSnapShot的创建流程。
class RootWindowContainer extends WindowContainer<DisplayContent>
implements DisplayManager.DisplayListener {
...
void performSurfacePlacementNoTrace() {
...
checkAppTransitionReady(surfacePlacer); // (1)
...
}
private void checkAppTransitionReady(WindowSurfacePlacer surfacePlacer) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayContent curDisplay = mChildren.get(i);
if (curDisplay.mAppTransition.isReady()) { // (2)
curDisplay.mAppTransitionController.handleAppTransitionReady();
...
}
}
...
}
...
}
public class AppTransitionController {
void handleAppTransitionReady() {
...
mService.mTaskSnapshotController.onTransitionStarting(mDisplayContent); // (3)
...
}
}
-
在布局循环开始之时,先检查过渡动画是否准备好了。
-
启动应用时prepare过渡动画,应用完成resume时execute过渡动画,所以此时可以handle过渡动画。总的来说就是三步曲prepare→execute→handle
-
此时动画已经配置到窗口了,通知TaskSnapshotController过渡动画正在开始。
class TaskSnapshotController {
void onTransitionStarting() {
handleClosingApps(displayContent.mClosingApps); // (1)
}
private void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
if (shouldDisableSnapshots()) {
return;
}
getClosingTasks(closingApps, mTmpTasks); // (2)
snapshotTasks(mTmpTasks); // (3)
mSkipClosingAppSnapshotTasks.clear();
}
void getClosingTasks(ArraySet<ActivityRecord> closingApps, ArraySet<Task> outClosingTasks) {
outClosingTasks.clear();
for (int i = closingApps.size() - 1; i >= 0; i--) {
final ActivityRecord activity = closingApps.valueAt(i);
final Task task = activity.getTask();
if (task != null && !task.isVisible() && !mSkipClosingAppSnapshotTasks.contains(task)) {
outClosingTasks.add(task);
}
}
}
}
-
ActivityRecord设置可见性时,如果ActivityRecord即将不可见,那么会将ActivityRecord添加到displayContent.mClosingApps。
-
并不是所有的closingApps都需要进行截图。因为截图是针对task的,只有即将不可见的task才需要截图。例如在应用内切换activity时,前一个Activity会被添加到closingApps,但是task还是可见的,所以并不会对前一个Activity进行截图。
-
对过滤之后的closingApps进行缓存和持久化存储操作。
class TaskSnapshotController {
void snapshotTasks(ArraySet<Task> tasks) {
snapshotTasks(tasks, false /* allowSnapshotHome */);
}
private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task task = tasks.valueAt(i);
final TaskSnapshot snapshot;
final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
if (snapshotHome) {
snapshot = snapshotTask(task); // (1)
} else {
switch (getSnapshotMode(task)) {
case SNAPSHOT_MODE_NONE: // (2)
continue;
case SNAPSHOT_MODE_APP_THEME:
snapshot = drawAppThemeSnapshot(task); // (3)
break;
case SNAPSHOT_MODE_REAL:
snapshot = snapshotTask(task); // (4)
break;
default:
snapshot = null;
break;
}
}
mCache.putSnapshot(task, snapshot); // (5)
if (!snapshotHome) {
mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); // (6)
task.onSnapshotChanged(snapshot);
}
}
}
int getSnapshotMode(Task task) {
final ActivityRecord topChild = task.getTopMostActivity();
if (!task.isActivityTypeStandardOrUndefined() && !task.isActivityTypeAssistant()) {
return SNAPSHOT_MODE_NONE;
} else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
return SNAPSHOT_MODE_APP_THEME;
} else {
return SNAPSHOT_MODE_REAL;
}
}
}
-
allowSnapshotHome在本流程中为false,所以不会进入该分支。如果锁屏被设置了密码,那么在息屏流程中就会设置allowSnapshotHome为true,会进入该分支。
-
如果ActivityType不是ACTIVITY_TYPE_UNDEFINED、ACTIVITY_TYPE_STANDARD、ACTIVITY_TYPE_ASSISTANT中的一种,那么是不会为其创建snapshot。很显然ACTIVITY_TYPE_HOME是被排除在外的。
-
SNAPSHOT_MODE_APP_THEME模式表示不希望使用窗口的内容构建snapshot,只使用App的theme。例如被标记了WindowManager.LayoutParams.FLAG_SECURE的安全窗口;像PipMenuActivity设置了ATMS.setDisablePreviewScreenshots()。
-
SNAPSHOT_MODE_REAL模式使用task的页面的内容构建snapshot。
-
缓存snapshot
-
如果不是home类型的task,持久化存储snapshot。
Note
|
解锁流程和点击home键流程,都不会为HomeActivity添加启动窗口,那为什么还要在息屏流程中设置allowSnapshotHome为true呢? |
class TaskSnapshotController {
...
private TaskSnapshot drawAppThemeSnapshot(Task task) {
...
final int color = ColorUtils.setAlphaComponent(
task.getTaskDescription().getBackgroundColor(), 255);
...
final RenderNode node = RenderNode.create("TaskSnapshotController", null);
...
final RecordingCanvas c = node.start(width, height);
c.drawColor(color); // (1)
...
final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
...
return new TaskSnapshot(..., hwBitmap.createGraphicBufferHandle(), ...); // (2)
}
...
}
-
设置页面颜色,color值从task背景色获取,alpha值为255不透明。
-
从Bitmap获取GraphicBuffer,作为TaskSnapShot的内容,所以drawAppThemeSnapshot()就是使用task背景色构建snapshot。
class TaskSnapshotController {
...
TaskSnapshot snapshotTask(Task task) {
return snapshotTask(task, PixelFormat.UNKNOWN);
}
TaskSnapshot snapshotTask(Task task, int pixelFormat) {
TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
...
final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
createTaskSnapshot(task, builder); // (1)
builder.setSnapshot(screenshotBuffer.getGraphicBuffer());
...
return builder.build();
}
SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
TaskSnapshot.Builder builder) {
Point taskSize = new Point();
final SurfaceControl.ScreenshotGraphicBuffer taskSnapshot = createTaskSnapshot(task,
mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize);
builder.setTaskSize(taskSize);
return taskSnapshot;
}
@Nullable
SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
float scaleFraction, int pixelFormat, Point outTaskSize) {
...
final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
SurfaceControl.captureLayersExcluding(
task.getSurfaceControl(), mTmpRect, scaleFraction,
pixelFormat, excludeLayers); // (2)
...
return screenshotBuffer;
}
...
}
-
构建TaskSnapShot的核心还是在于GraphicBuffer的获取,继续深入探索。
-
使用Task的SurfaceControl获取GraphicBuffer,这就解释的通了,窗口的buffer本就是SurfaceControl提供的。
class TaskSnapshotCache {
...
void putSnapshot(Task task, TaskSnapshot snapshot) {
final CacheEntry entry = mRunningCache.get(task.mTaskId);
if (entry != null) {
mAppTaskMap.remove(entry.topApp); // (1)
}
final ActivityRecord top = task.getTopMostActivity();
mAppTaskMap.put(top, task.mTaskId); // (2)
mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top)); // (3)
}
...
}
-
更新操作,为了将新的ActivityRecord和task绑定在一起。
-
ActivityRecord和taskId建立映射关系,为什么不直接使用RunningCache,非要整两个map呢?这是为了监听通过ActivityRecord的remove来更新RunningCache。
-
taskId和CacheEntry建立映射关系,为什么需要使用CacheEntry呢?这是因为需要通过taskId找到ActivityRecord,这样TaskId和ActivityRecord完成了双向绑定。在ActivityRecord被移除时,可以顺着两个map移除snapshot;在添加新的snapshot时,将旧的ActivityRecord移除,当然CacheEntry会直接被新的覆盖。
Android的持久化存储基本都是使用Persister线程+Item队列的模式。
-
考虑到耗时和多线程的原因,创建一个线程用于处理各个场景创建的文件操作任务
-
把文件操作任务抽象成一个Item,这里是WriteQueueItem。然后派生不同的Item描述不同类型的操作。
class TaskSnapshotPersister {
...
void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
synchronized (mLock) {
mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
}
}
...
private class StoreWriteQueueItem extends WriteQueueItem {
...
@Override
void write() { // (1)
if (!createDirectory(mUserId)) {
Slog.e(TAG, "Unable to create snapshot directory for user dir="
+ getDirectory(mUserId));
}
boolean failed = false;
if (!writeProto()) {
failed = true;
}
if (!writeBuffer()) {
failed = true;
}
if (failed) {
deleteSnapshot(mTaskId, mUserId);
}
}
...
}
...
@GuardedBy("mLock")
private void sendToQueueLocked(WriteQueueItem item) {
mWriteQueue.offer(item); // (2)
...
mLock.notifyAll(); // (3)
...
}
...
private Thread mPersister = new Thread("TaskSnapshotPersister") {
public void run() {
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
WriteQueueItem next;
synchronized (mLock) {
...
next = mWriteQueue.poll();
...
}
if (next != null) {
...
next.write(); // (4)
...
}
synchronized (mLock) {
final boolean writeQueueEmpty = mWriteQueue.isEmpty();
if (!writeQueueEmpty && !mPaused) {
continue;
}
...
mLock.wait(); // (5)
...
}
}
}
};
}
-
StoreWriteQueueItem描述了存储磁盘的任务,在重写的write方法中,会创建文件存储snapshot的task信息,buffer信息。
-
入队操作
-
唤醒Persist线程
-
取出Item任务后执行
-
执行完队列的所有任务进入等待状态,等待另一个线程来唤醒
在什么时候我们需要移除TaskSnapShot呢?那自然是task被销毁的时候,我们接下来分析以下两种场景下,TaskSnapShot是如何被移除的:
-
从Back键退出应用
-
从多任务移除应用
一般来讲,Activity对back事件的处理是移除Activity。TaskSnapShotController对Activity的移除比较感兴趣,所以在Activity被移除时TaskSnapShotController会尝试移除TaskSnapShot的缓存。
public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
...
@Override
public final void activityDestroyed(IBinder token) {
...
final ActivityRecord activity = ActivityRecord.forTokenLocked(token);
if (activity != null) {
activity.destroyed("activityDestroyed");
}
...
}
...
}
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
...
void destroyed(String reason) {
...
removeFromHistory(reason);
...
}
void removeFromHistory(String reason) {
...
removeAppTokenFromDisplay();
...
}
private void removeAppTokenFromDisplay() {
...
dc.removeAppToken(appToken.asBinder());
...
}
}
class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>
implements WindowManagerPolicy.DisplayContentInfo {
...
void removeAppToken(IBinder binder) {
...
activity.onRemovedFromDisplay();
...
}
...
}
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
...
void onRemovedFromDisplay() {
...
mWmService.mTaskSnapshotController.onAppRemoved(this);
...
}
}
class TaskSnapshotController {
...
void onAppRemoved(ActivityRecord activity) {
mCache.onAppRemoved(activity);
}
...
}
class TaskSnapshotCache {
...
void onAppRemoved(ActivityRecord activity) {
final Integer taskId = mAppTaskMap.get(activity); // (1)
if (taskId != null) {
removeRunningEntry(taskId); // (2)
}
}
void removeRunningEntry(int taskId) {
final CacheEntry entry = mRunningCache.get(taskId);
if (entry != null) {
mAppTaskMap.remove(entry.topApp);
mRunningCache.remove(taskId);
}
}
...
}
-
TaskSnapShot的创建流程中,最后一次可见的ActivityRecord与taskId建立映射,所以当且仅当之前建立映射的ActivityRecord被移除时,才会移除缓存。
-
断开ActivityRecord、taskId与TaskSnapShot之间的映射关系。
从多任务移除应用往往意味着这个task被移除了。即也不需要从热启动恢复这个task,不需要从多任务恢复这个task,那么TaskSnapShot的缓存和持久存储都需要被移除。
public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
...
@Override
public boolean removeTask(int taskId) {
...
return mStackSupervisor.removeTaskById(taskId, true, REMOVE_FROM_RECENTS,
"remove-task");
...
}
...
}
public class ActivityStackSupervisor implements RecentTasks.Callbacks {
...
boolean removeTaskById(int taskId, boolean killProcess, boolean removeFromRecents,
String reason) {
...
removeTask(task, killProcess, removeFromRecents, reason);
...
}
void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
...
cleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);
...
}
void cleanUpRemovedTaskLocked(Task task, boolean killProcess, boolean removeFromRecents) {
...
if (removeFromRecents) {
mRecentTasks.remove(task);
}
...
}
...
}
class RecentTasks {
...
void remove(Task task) {
mTasks.remove(task);
notifyTaskRemoved(task, false /* wasTrimmed */, false /* killProcess */);
}
private void notifyTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess) {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed, killProcess);
}
mTaskNotificationController.notifyTaskListUpdated();
}
...
}
public class ActivityStackSupervisor implements RecentTasks.Callbacks {
...
@Override
public void onRecentTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess) {
...
task.removedFromRecents();
}
...
}
class Task extends WindowContainer<WindowContainer> {
...
void removedFromRecents() {
...
mAtmService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents(
mTaskId, mUserId);
}
...
}
class TaskSnapshotController {
...
void notifyTaskRemovedFromRecents(int taskId, int userId) {
mCache.onTaskRemoved(taskId); // (1)
mPersister.onTaskRemovedFromRecents(taskId, userId);
}
...
}
class TaskSnapshotCache {
void onTaskRemoved(int taskId) {
removeRunningEntry(taskId); // (2)
}
void removeRunningEntry(int taskId) {
final CacheEntry entry = mRunningCache.get(taskId);
if (entry != null) {
mAppTaskMap.remove(entry.topApp);
mRunningCache.remove(taskId);
}
}
}
-
当task在最近任务被移除时,移除缓存
-
移除两个map中的映射关系。
class TaskSnapshotPersister {
...
void onTaskRemovedFromRecents(int taskId, int userId) {
synchronized (mLock) {
...
sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId)); // (1)
}
}
private class DeleteWriteQueueItem extends WriteQueueItem {
private final int mTaskId;
private final int mUserId;
DeleteWriteQueueItem(int taskId, int userId) {
mTaskId = taskId;
mUserId = userId;
}
@Override
void write() {
deleteSnapshot(mTaskId, mUserId); // (2)
}
}
private void deleteSnapshot(int taskId, int userId) {
final File protoFile = getProtoFile(taskId, userId);
final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
protoFile.delete();
if (bitmapLowResFile.exists()) {
bitmapLowResFile.delete();
}
final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
if (bitmapFile.exists()) {
bitmapFile.delete();
}
}
...
}
-
在之前的TaskSnapShot的持久化存储流程中有介绍Item队列会被Persist线程按序处理
-
通过taskId和userId找到TaskSnapShot文件存储的位置,并将其删除。
本文分析的内容主要为以下几点:
-
StartingWindow的框架:StartingWindow的类型、组成、创建和移除时机。
-
SplashScreen和SnapShot类型的启动窗口:
-
StartingWindow类型的获取
-
分别使用PhoneWindow和GraphicBuffer绘制启动窗口
-
移除StartingWindow的时机
-
-
TaskSnapShot的缓存和持久化存储的管理机制
-
创建TaskSnapShot的时机
-
缓存中ActivityRecord和TaskSnapShot的映射关系
-
持久化存储使用的线程+Item机制
-
移除缓存和持久化存储的时机
-
还需要了解的其他内容:
-
过渡动画准备和执行流程
-
StartingWindow对Home Activity的特殊处理