startingwindow介绍

startingwindow是什么

在activity真正显示之前,可能要处理大量耗时任务,如进程创建,资源加载,窗口绘制等。所以在窗口的过渡动画完成之后,可能应用还没有完成页面的绘制,我们需要一个页面来等待真正的activity显示。或者说窗口过渡的动画使用什么素材?

startingwindow的存在就是为了解决这样的问题,它是应用启动时窗口的一个过渡

startingwindow的组成

和activity的窗口一样,Startingwindow也是又windowState和surface构成。窗口类型时TYPE_APPLICATION_STARTING,startingwindow类型有三种

static final int STARTING_WINDOW_TYPE_NONE = 0;
//不启动startingwindow,常见于应用内的activity切换
static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1;
//快照启动窗口,显示的内容为最近一次的可见内容的快照。使用场景如task从后台到前台的切换,屏幕解锁。
static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2;
//闪屏启动窗口,显示的内容时空白的窗口,背景和应用的主题有关。使用场景如应用冷启动

startingwindow的框架

数据结构

系统侧

  • Task

    存放task的activityRecord的容器,也是处理activityRecord生命周期的主要参与者。startingwindow的启动流程起点就是task.startActivityLocked()
    (注:Android12之后的版本已经没有ActivityStack这个类,ActivityStack和Task统一由Task表示,因此为task.startActivityLocked())

  • ActivityRecord

    系统进程中的Activity,也就是窗口容器,activity窗口和启动窗口都是它的child,因此启动窗口的添加和移除都是由ActivityRecord负责。

  • WindowState

    系统进程中的窗口,在窗口管理系统中时空指页面大小位置等属性的基本单元。startingwindow启动中回创建一个TYPE_APPLICATION_STARTING窗口类型的WindowState

  • StartingData

    抽象了startingwindow的数据模型,负责构造startingsurface

  • SplashScreenStartingData

    闪屏类型启动窗口的startingData实现,封装了闪屏类型启动窗口所需要的数据,如theme,icon,windowflags等等,这些数据来源于ActivityRecord。

  • SnapshotStartingData

    快照类型启动窗口的startingData实现,持有了TaskSnapshot

  • StartingSurfaceController
    由TaskOrganizer.java和TaskOrganizerController.java打通从系统框架到wmshell的通路,从而与wmshell侧的StartingWindowController互交

wmshell侧

  • StartingWindowController
    startingwindow的添加和移除最终的调用都在这里

  • StartingSurfaceDrawer
    startingwindow的添加和移除的实现

流程简述

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

startingwindow的创建与移除主要是通过StartingWindowController的create和remove实现的。
在Launcher启动App场景下,startingwindow的启动入口是ActivityStarter.startActivityLocked()或者Task.startActivityLocked(),移除入口是WindowManagerService.finishDrawing()
在recents启动APP、屏幕解锁场景,startingwindow的启动和移除入口是其他通道,但内部实现一致。

添加流程

移除流程

startingwindow的添加

前置流程

1.启动或者切换到另一个界面,ATMS会执行startActivity()函数
关键代码:ActivityTaskManagerService.java中的startActivityAsUser方法返回值。ActivityStartController.java的obtainStarter方法,返回的ActivityStarter对象。即实际调用了ActivityStarter的execute方法

 private int startActivityAsUser(IApplicationThread caller, String callingPackage,@Nullable String callingFeatureId, Intent intent, String resolvedType,IBinder resultTo, String resultWho, int requestCode, int startFlags,ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {......// TODO: Switch to user app stacks here.return getActivityStartController().obtainStarter(intent, "startActivityAsUser").setCaller(caller).setCallingPackage(callingPackage).setCallingFeatureId(callingFeatureId).setResolvedType(resolvedType).setResultTo(resultTo).setResultWho(resultWho).setRequestCode(requestCode).setStartFlags(startFlags).setProfilerInfo(profilerInfo).setActivityOptions(bOptions).setUserId(userId).execute();}

ActivityStarter.java后续方法调用链
execute()->executeRequest()->startActivityUnchecked()->startActivityInner()->startActivityLocked()
startActivityInner中分两步走
调用resumeFocusedTasksTopActivities()走Activity启动流程
调用startActivityLocked()走启动窗口流程
我们这里只关注启动窗口的流程

2.系统会把activity添加到对应的task中,并调用activityRecord的showStartingWindow(),通知显示startingwindow
ActivityStarter.java中的startActivityLocked()调用的是Task.java中的startActivityLocked()

该方法中有一个关键变量boolean doShow = true;这个doShow指是否执行ActivityRecord.showStartingWindow方法,在某些场景下会被置为false

    void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask,boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) {......boolean doShow = true;if (newTask) {// Even though this activity is starting fresh, we still need// to reset it to make sure we apply affinities to move any// existing activities from other tasks in to it.// If the caller has requested that the target task be// reset, then do so.if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {resetTaskIfNeeded(r, r);doShow = topRunningNonDelayedActivityLocked(null) == r;}} else if (options != null && options.getAnimationType()== ActivityOptions.ANIM_SCENE_TRANSITION) {doShow = false;}if (r.mLaunchTaskBehind) {// Don't do a starting window for mLaunchTaskBehind. More importantly make sure we// tell WindowManager that r is visible even though it is at the back of the root// task.r.setVisibility(true);ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);// Go ahead to execute app transition for this activity since the app transition// will not be triggered through the resume channel.mDisplayContent.executeAppTransition();} else if (SHOW_APP_STARTING_PREVIEW && doShow) {// Figure out if we are transitioning from another activity that is// "has the same starting icon" as the next one.  This allows the// window manager to keep the previous window it had previously// created, if it still had one.Task baseTask = r.getTask();if (baseTask.isEmbedded()) {// If the task is embedded in a task fragment, there may have an existing// starting window in the parent task. This allows the embedded activities// to share the starting window and make sure that the window can have top// z-order by transferring to the top activity.baseTask = baseTask.getParent().asTaskFragment().getTask();}final ActivityRecord prev = baseTask.getActivity(a -> a.mStartingData != null && a.showToCurrentUser());mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask,isTaskSwitch, sourceRecord);}......}

调用StartingSurfaceController的showStartingWindow()
mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask,isTaskSwitch, sourceRecord);
实际实现是在ActivityRecord中
ActivityRecord.java#showStartingWindow()

void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,boolean processRunning, boolean startActivity, ActivityRecord sourceRecord,ActivityOptions candidateOptions) {if (mTaskOverlay) {// We don't show starting window for overlay activities.return;}final ActivityOptions startOptions = candidateOptions != null? candidateOptions : mPendingOptions;if (startOptions != null&& startOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {// Don't show starting window when using shared element transition.return;}final int splashScreenTheme = startActivity ? getSplashscreenTheme(startOptions) : 0;final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme,splashScreenTheme);mSplashScreenStyleSolidColor = shouldUseSolidColorSplashScreen(sourceRecord, startActivity,startOptions, resolvedTheme);final boolean activityCreated =mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal();// If this activity is just created and all activities below are finish, treat this// scenario as warm launch.final boolean newSingleActivity = !newTask && !activityCreated&& task.getActivity((r) -> !r.finishing && r != this) == null;final boolean scheduled = addStartingWindow(packageName, resolvedTheme,prev, newTask || newSingleActivity, taskSwitch, processRunning,allowTaskSnapshot(), activityCreated, mSplashScreenStyleSolidColor, allDrawn);if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) {Slog.d(TAG, "Scheduled starting window for " + this);}}

关键代码:

final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme,splashScreenTheme);

这个方法调用了validateStartingWindowTheme方法,判断启动窗口的几种不添加场景

windowIsTranslucent ----透明窗口,应用设了透明属性,空Activity等(应用侧有属性配置)
应用侧设置透明属性

<style name="APPTheme" parent="@android:style/Test"><item name="android:windowDisablePreview">true</item></style>

windowIsFloating ----浮窗相关场景

windowShowWallpaper ----带wallpaper属性,比如桌面,锁屏等等

windowDisableStarting ----主动禁用startingwindow,应用主动禁用启动窗口(应用侧有属性配置)
应用侧配置禁用启动窗口

<style name="APPTheme" parent="@android:style/Test"><item name="android:windowDisablePreview">true</item></style>

以上四个参数任意一个为true则不添加启动窗口,这里主要针对的是冷启动的Splash Screen的添加,热启动不受窗口属性的影响

添加startingwindow的调用
final boolean scheduled = addStartingWindow(packageName, resolvedTheme,prev, newTask || newSingleActivity, taskSwitch, processRunning,allowTaskSnapshot(), activityCreated,mSplashScreenStyleSolidColor, allDrawn);

以上是T的代码,S的有所不同

但最终都是让ActivityRecord把各种activity相关属性传入到了addstartingwindow,为了就是让startingwindow在显示上尽可能的和实际显示的activity相似

3.核心方法,判断是否需要添加startingwindow已及其类型

ActivityRecord.java#addStartingWindow

boolean addStartingWindow(String pkg, int resolvedTheme, ActivityRecord from, boolean newTask,boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot,boolean activityCreated, boolean isSimple,boolean activityAllDrawn) {// If the display is frozen, we won't do anything until the actual window is// displayed so there is no reason to put in the starting window.if (!okToDisplay()) {return false;}if (mStartingData != null) {return false;}final WindowState mainWin = findMainWindow();if (mainWin != null && mainWin.mWinAnimator.getShown()) {// App already has a visible window...why would you want a starting window?return false;}final TaskSnapshot snapshot =mWmService.mTaskSnapshotController.getSnapshot(task.mTaskId, task.mUserId,false /* restoreFromDisk */, false /* isLowResolution */);final int type = getStartingWindowType(newTask, taskSwitch, processRunning,allowTaskSnapshot, activityCreated, activityAllDrawn, snapshot);//TODO(191787740) Remove for T+final boolean useLegacy = type == STARTING_WINDOW_TYPE_SPLASH_SCREEN&& mWmService.mStartingSurfaceController.isExceptionApp(packageName, mTargetSdk,() -> {ActivityInfo activityInfo = intent.resolveActivityInfo(mAtmService.mContext.getPackageManager(),PackageManager.GET_META_DATA);return activityInfo != null ? activityInfo.applicationInfo : null;});final int typeParameter = StartingSurfaceController.makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning,allowTaskSnapshot, activityCreated, isSimple, useLegacy, activityAllDrawn,type, packageName, mUserId);if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {if (isActivityTypeHome()) {// The snapshot of home is only used once because it won't be updated while screen// is on (see {@link TaskSnapshotController#screenTurningOff}).mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);if ((mDisplayContent.mAppTransition.getTransitFlags()& WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) {// Only use snapshot of home as starting window when unlocking directly.return false;}}return createSnapshot(snapshot, typeParameter);}// Original theme can be 0 if developer doesn't request any theme. So if resolved theme is 0// but original theme is not 0, means this package doesn't want a starting window.if (resolvedTheme == 0 && theme != 0) {return false;}if (from != null && transferStartingWindow(from)) {return true;}// There is no existing starting window, and we don't want to create a splash screen, so// that's it!if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {return false;}ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");mStartingData = new SplashScreenStartingData(mWmService, resolvedTheme, typeParameter);scheduleAddStartingWindow();return true;}

通过获取startingwindow类型来添加启动窗口
final int type = getStartingWindowType(newTask, taskSwitch, processRunning,allowTaskSnapshot, activityCreated, activityAllDrawn, snapshot);
该方法用来判断startingwindow窗口的类型:splash、snapshot、none

 private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,boolean allowTaskSnapshot, boolean activityCreated, boolean activityAllDrawn,TaskSnapshot snapshot) {// A special case that a new activity is launching to an existing task which is moving to// front. If the launching activity is the one that started the task, it could be a// trampoline that will be always created and finished immediately. Then give a chance to// see if the snapshot is usable for the current running activity so the transition will// look smoother, instead of showing a splash screen on the second launch.if (!newTask && taskSwitch && processRunning && !activityCreated && task.intent != null&& mActivityComponent.equals(task.intent.getComponent())) {final ActivityRecord topAttached = task.getActivity(ActivityRecord::attachedToProcess);if (topAttached != null) {if (topAttached.isSnapshotCompatible(snapshot)// This trampoline must be the same rotation.&& mDisplayContent.getDisplayRotation().rotationForOrientation(mOrientation,mDisplayContent.getRotation()) == snapshot.getRotation()) {return STARTING_WINDOW_TYPE_SNAPSHOT;}// No usable snapshot. And a splash screen may also be weird because an existing// activity may be shown right after the trampoline is finished.return STARTING_WINDOW_TYPE_NONE;}}final boolean isActivityHome = isActivityTypeHome();if ((newTask || !processRunning || (taskSwitch && !activityCreated))&& !isActivityHome) {return STARTING_WINDOW_TYPE_SPLASH_SCREEN;}if (taskSwitch) {if (allowTaskSnapshot) {if (isSnapshotCompatible(snapshot)) {return STARTING_WINDOW_TYPE_SNAPSHOT;}if (!isActivityHome) {return STARTING_WINDOW_TYPE_SPLASH_SCREEN;}}if (!activityAllDrawn && !isActivityHome) {return STARTING_WINDOW_TYPE_SPLASH_SCREEN;}}return STARTING_WINDOW_TYPE_NONE;}boolean isSnapshotCompatible(TaskSnapshot snapshot) {if (snapshot == null) {return false;}if (!snapshot.getTopActivityComponent().equals(mActivityComponent)) {// Obsoleted snapshot.return false;}final int rotation = mDisplayContent.rotationForActivityInDifferentOrientation(this);final int currentRotation = task.getWindowConfiguration().getRotation();final int targetRotation = rotation != ROTATION_UNDEFINED// The display may rotate according to the orientation of this activity.? rotation// The activity won't change display orientation.: currentRotation;if (snapshot.getRotation() != targetRotation) {return false;}final Rect taskBounds = task.getBounds();int w = taskBounds.width();int h = taskBounds.height();final Point taskSize = snapshot.getTaskSize();if ((Math.abs(currentRotation - targetRotation) % 2) == 1) {// Flip the size if the activity will show in 90 degree difference.final int t = w;w = h;h = t;}// Task size might be changed with the same rotation such as on a foldable device.return Math.abs(((float) taskSize.x / Math.max(taskSize.y, 1))- ((float) w / Math.max(h, 1))) <= 0.01f;}

关键参数:newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated, snapshot

1)newTask、进程未启动、冷启动、切换task且新创建activity时,获得type为splash screen

  • 新起一个task
  • 冷启动应用
  • task切换且新建activity

代码层面 Splash Screen需满足的条件
前置条件:!isActivityTypeHome() --非Launcher桌面
newTask --新的task
!processRunning --进程不存在
taskSwitch && !activityCreated --task切换 且 Activity未创建

2)切换task且允许snapshot时,尝试获取type为snapshot(判断isSnapshotCompatible是否满足,是指activity的屏幕方向和截图的屏幕方向是否一致,如果不一致自然不能使用截图作为启动窗口)

  • 非splash场景、且task切换

代码层面 SnapshotStartWindow需满足的条件
taskSwitch && allowTaskSnapshot – task切换 且 allowTaskSnapshot为true
allowTaskSnapshot --非浮窗模式 且 newIntents为空才能满足此条件

类型讲完了,我们继续看看addStartingWindow的后续
1)如果是snapshot类型,则执行createSnapshot,创建startingwindow窗口

 if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {if (isActivityTypeHome()) {......}return createSnapshot(snapshot, typeParameter);}
private boolean createSnapshot(TaskSnapshot snapshot, int typeParams) {if (snapshot == null) {return false;}ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SnapshotStartingData");mStartingData = new SnapshotStartingData(mWmService, snapshot, typeParams);if (task.forAllLeafTaskFragments(TaskFragment::isEmbedded)) {// Associate with the task so if this activity is resized by task fragment later, the// starting window can keep the same bounds as the task.associateStartingDataWithTask();}scheduleAddStartingWindow();return true;}

mStartingData = new SnapshotStartingData(mWmService, snapshot, typeParams);
在保存Snapshot的StartingData之后,异步添加启动窗口scheduleAddStartingWindow()

2)创建Splash类型的startingwindow

mStartingData = new SplashScreenStartingData(mWmService, resolvedTheme, typeParameter);
scheduleAddStartingWindow();

同样的在保存SplashScreen的StartingData之后,异步添加启动窗口,这是为了不让启动窗口的绘制阻塞activity的启动

3)type为none,即不创建startingwindow(此场景常见于应用内的activity切换)

4.执行scheduleAddStartingWindow()函数,添加startingwindow。它会把消息mAddStartingWindow发送到android.anim线程中处理

void scheduleAddStartingWindow() {mAddStartingWindow.run();
}
private class AddStartingWindow implements Runnable {@Overridepublic void run() {// Can be accessed without holding the global lockfinal StartingData startingData;synchronized (mWmService.mGlobalLock) {// There can only be one adding request, silly caller!if (mStartingData == null) {// Animation has been canceled... do nothing.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);StartingSurfaceController.StartingSurface surface = null;try {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) {// If the window was successfully added, then we need to remove it.if (mStartingData == null) {ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Aborted starting %s: startingData=%s",ActivityRecord.this, mStartingData);mStartingWindow = null;mStartingData = null;abort = true;} else {mStartingSurface = surface;}if (!abort) {ProtoLog.v(WM_DEBUG_STARTING_WINDOW,"Added starting %s: startingWindow=%s startingView=%s",ActivityRecord.this, mStartingWindow, mStartingSurface);}}if (abort) {surface.remove(false /* prepareAnimation */);}} else {ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Surface returned was null: %s",ActivityRecord.this);}}}private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();

mAddStartingWindow是AddStartingWindow创建的对象,其实现了Runnable接口
创建startingwindowsurface = startingData.createStartingSurface(ActivityRecord.this);
如果当前是splash类型,那么会执行SplashScreenStartingData的createStartingSurface(),创建startingwindow;如果是snapshot类型,则执行SnapshotStartingData的createStartingSurface(),创建startingwindow。(startingData为SplashScreenStartingData和SnapshotStartingData父类,根据前说的获取的类型来创建相应的startingwindow)

创建splash类型的startingwindow

ActivityRecord.java
AddStartingWindow复写run方法调用了
surface = startingData.createStartingSurface(ActivityRecord.this);
调用链
SplashScreenStartingData.java#createStartingSurface

StartingSurfaceController.java#createSplashScreenStartingSurface
通过TaskOrganizerController.java最终调用到wmshell中的StartingWindowController.java的addStartingWindow
wmshell流程开始
StartingWindowController.java
addStartingWindow
StartingSurfaceDrawer.java
addSplashScreenStartingWindow
1.createContentView
SplashscreenContentDrawer.java
createContentView-> makeSplashScreenContentView
2.addWindow
调用mWindowManagerGlobal.addView(view, params, display, null /* parentWindow */, context.getUserId());
窗口添加流程
WindowManagerGlobal.java
addView.java
ViewRootImpl
setView->addToDisplayAsUser
Session.java
addToDisplayAsUser
WindowManagerService.java
addWindow
添加启动窗口
ActivityRecord.java
attachStartingWindow

创建snapshot类型的startingwindow

ActivityRecord.java
addstartingwindow方法中调用createSnapshot
mStartingData = new SnapshotStartingData(mWmService, snapshot, typeParams);
调用链
SnapshotStartingData.java#createStartingSurface

StartingSurfaceController.java#createTaskSnapshotSurface

通过TaskOrganizerController.java最终调用到wmshell中的StartingWindowController.java的addStartingWindow
wmshell流程开始
StartingWindowController.java
addStartingWindow
StartingSurfaceDrawer.java
makeTaskSnapshotWindow->create
TaskSnapshotWindow.java
create
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId, info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls);
窗口添加流程
Session.java
addToDisplayAsUser
WindowManagerService.java
addWindow
添加启动窗口
ActivityRecord.java
attachStartingWindow

总结

两种不同类型的启动窗口最终都会调用到StartingWindowController.java的addStartingWindow,只是传入参数不同,后面会根据类型不同调用不同的方法

StartingSurfaceDrawer.java中调用不同的方法

startingwindow的移除

前置流程

1、应用界面绘制完成,ViewRootImpl的finishDrawing(),去通知wms
ViewRootImpl.java
reportDrawFinished->finishDrawing
关键代码
mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction, seqId);
2、wms接收到应用绘制完成的消息后,调用requestTraversal()请求刷新窗口
WindowManagerService.java
finishDrawingWindow->requestTraversal
关键代码
mWindowPlacerLocked.requestTraversal();
3、窗口刷新过程中,会调用mApplySurfaceChangesTransaction
WindowSurfacePlacer.java
requestTraversal()->performSurfacePlacement()
关键代码
mService.mAnimationHandler.post(mPerformSurfacePlacement);

    private class Traverser implements Runnable {@Overridepublic void run() {synchronized (mService.mGlobalLock) {performSurfacePlacement();}}}private final Traverser mPerformSurfacePlacement = new Traverser();

performSurfacePlacement()->performSurfacePlacementLoop()
关键代码
mService.mRoot.performSurfacePlacement();
RootWindowContainer.java
performSurfacePlacement()->performSurfacePlacementNoTrace()->applySurfaceChangesTransaction()
DisplayContent.java
applySurfaceChangesTransaction()->commitFinishDrawingLocked()
关键代码
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);

private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {......// Moved from updateWindowsAndWallpaperLocked().if (w.mHasSurface) {// Take care of the window being ready to display.final boolean committed = winAnimator.commitFinishDrawingLocked();......}......};

4、窗口绘制完成后,接着调用winAnimator的commitFinishDrawingLocked(),状态值mDrawState改为READY_TO_SHOW,准备显示
WindowStateAnimator.java
commitFinishDrawingLocked()->performShowLocked()
关键代码
mDrawState = READY_TO_SHOW;
result = mWin.performShowLocked();

5、如果当前是非startingwindow类型的窗口,则调用函数onFirstWindowDrawn(),开始移除startingwindow窗口。因为这个时候主界面已经绘制完成,不在需要显示startingwindow
WindowState.java
performShowLocked()->onFirstWindowDrawn()
关键代码

final int drawState = mWinAnimator.mDrawState;if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {if (mAttrs.type != TYPE_APPLICATION_STARTING) {mActivityRecord.onFirstWindowDrawn(this);} else {mActivityRecord.onStartingWindowDrawn();}}

ActivityRecord.java
onFirstWindowDrawn->removeStartingWindow,这里开始区分有无退出动画
如果有startingwindow退出动画则会多走transferSplashScreenIfNeeded的流程,该流程仅限于冷启动
从代码中可以看出mHandleExitSplashScreen变量决定是否有启动动画,跟踪代码可以发现,该变量的值取决于应用是否设置退出动画splashScreen.setOnExitAnimationListener(this::onSplashScreenExit);

退出动画流程(仅限冷启动)

以ActivityRecord#removeStartingWindow为起点
ActivityRecord.java
removeStartingWindow->transferSplashScreenIfNeeded->requestCopySplashScreen
通过TaskOrganizerController.java最终调用到wmshell中的
ShellTaskOrganizer.java
copySplashScreenView
StartingWindowController.java
copySplashScreenView
StartingSurfaceDrawer.java
copySplashScreenView
调用ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);,回到system_server进程
ActivityTaskManagerService.java
onSplashScreenViewCopyFinished
ActivityRecord.java
onCopySplashScreenFinish

mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,TransferSplashScreenViewStateItem.obtain(parcelable,windowAnimationLeash));

调用到应用进程
TransferSplashScreenViewStateItem.java
execute
ActivityThread.java
handleAttachSplashScreenView->createSplashScreen->syncTransferSplashscreenViewTransaction->reportSplashscreenViewShown
ActivityClient.java
reportSplashScreenAttached
ActivityClientController.java
splashScreenAttached
ActivityThread.java
splashScreenAttachedLocked->onSplashScreenAttachComplete->removeStartingWindowAnimation

继续往下走,移除启动窗口

无退出动画流程

ActivityRecord.java
removeStartingWindowAnimation->remove
StartingSurfaceController.java
remove->removeStartingWindow

通过TaskOrganizerController.java最终调用到wmshell中的StartingWindowController.java中removeStartingWindow
StartingWindowController.java
removeStartingWindow
StartingSurfaceDrawer.java
removeStartingWindow->removeWindowSynced
6、在removeWindowSynced()中根据当前的startingwindow的类型,选择移除方式

splash screen类型移除


移除splash类型的startingwindow
StartingSurfaceDrawer.java
removeWindowSynced->removeWindowInner
调用mWindowManagerGlobal.removeView(decorView, false /* immediate */);移除
WindowManagerGlobal.java
removeView

snapshot类型移除


移除snapshot类型的startingwindow
StartingSurfaceDrawer.java
removeWindowSynced->scheduleRemove
TaskSnapshotWindow.java
scheduleRemove->removeImmediately
调用mSession.remove(mWindow);移除
WindowManagerService.java
removeWindow

总结

startingwindow有无退出渐变动画(仅限冷启动),依赖mHandleExitSplashScreen值,该值取决于应用是否设置退出动画splashScreen.setOnExitAnimationListener(this::onSplashScreenExit);
两种不同的移除方式最终会从removeWindowSynced方法中选择当前的调用

startingwindow去除启动大图标

问题

去掉启动窗口的大图标显示,如果应用有定制则保留其启动页面

思路

在SplashScreen添加流程中,会通过SplashscreenContentDrawer中调用
createContentView-> makeSplashScreenContentView创建SplashScreen图,在makeSplashScreenContentView方法中通过getWindowAttrs获取窗口属性

修改

通过windowSplashScreenAnimatedIcon属性,来获取当前启动页

attrs.mSplashScreenIcon = safeReturnAttrDefault((def) -> typedArray.getDrawable(R.styleable.Window_windowSplashScreenAnimatedIcon), null);

如果有定制,则该属性不为空

学习过程中遇到的问题

关于启动窗口添加AddStartingWindow中线程

在ActivityRecord.java中

   private class AddStartingWindow implements Runnable {@Overridepublic void run() {// Can be accessed without holding the global lockfinal StartingData startingData;synchronized (mWmService.mGlobalLock) {// There can only be one adding request, silly caller!if (mStartingData == null) {// Animation has been canceled... do nothing.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);StartingSurfaceController.StartingSurface surface = null;try {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) {// If the window was successfully added, then we need to remove it.if (mStartingData == null) {ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Aborted starting %s: startingData=%s",ActivityRecord.this, mStartingData);mStartingWindow = null;mStartingData = null;abort = true;} else {mStartingSurface = surface;}if (!abort) {ProtoLog.v(WM_DEBUG_STARTING_WINDOW,"Added starting %s: startingWindow=%s startingView=%s",ActivityRecord.this, mStartingWindow, mStartingSurface);}}if (abort) {surface.remove(false /* prepareAnimation */);}} else {ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Surface returned was null: %s",ActivityRecord.this);}}}

abort参数代表什么?
正常情况下,snapshot值,会进来这个地方,证明已经有了可以创建startingwindow中的startingView(代码中变量是startingSurface),等addWindow成功后才会为mStartingWindow 赋值
WMS中调用attachStartingWindow

该方法在ActivityRecord.java中实现

    void attachStartingWindow(@NonNull WindowState startingWindow) {startingWindow.mStartingData = mStartingData;mStartingWindow = startingWindow;if (mStartingData != null && mStartingData.mAssociatedTask != null) {attachStartingSurfaceToAssociatedTask();}}

回到前面AddStartingWindow的run方法synchronized (mWmService.mGlobalLock)可能会出现等锁时间长的异常情况,这时如果mStartingData为空了,就代表添加启动窗口失败,它就要移除前面创建的surface对像,即surface.remove(false /* prepareAnimation */);

移除启动窗口调用差异

splash screen

snapshot

splash screen是通过removeView的方式移除,而snapshot是通过removeWindow方式移除


从代码中可以看出splash screen是通过构建view的方式创建,所以removeView


而这里可以看出snapshot是直接将截图添加到了window中,所以removeWindow

问题分析处理

1、温启动应用时启动慢/白屏

应用保活机制影响:APP被后台查杀,发起添加startingwindow的Activity变了,对应的Activity的主题里没有启动窗口配置项,所以默认获取了系统的白屏启动窗口。
以淘宝为例,温启动场景下淘宝的Welcome activty被重新创建了,当点击应用桌面图标打开淘宝时:Welcome不会再去创建,直接创建了TBMainActivity,新创建的这个activity没有startingwindow相关的配置,所以用了系统默认的startingwindow,因此显示了白屏。
按照谷歌当前的业务逻辑,为温启动强制添加splash screen:个别主界面和启动页界面不一致的保活应用可能会出现温启动白屏现象。getStartingWindowType中修改

添加判断条件如果snapshot 不为空则,把startingwindow改为空,即不显示启动窗口
if(snapshot != null) return STARTING_WINDOW_TYPE_NONE;

2、startingwindow引起的定屏问题
启动应用过程中添加了startingwindow,应用界面未绘制完成造成startingwindow未移除:
这是因为应用启动出现了异常,可能是应用发生了ANR,或者应用的生命周期未走完。属于应用问题,需要处理。

3、屏幕解锁后低概率出现短暂黑屏
可以dump window看,如果在只有一个main窗口的情况下,mNumInterestingWindows为2,正常应该是1

在ActivityRecord.updateDrawnWindowStates中判断如果是StartingWindow类型的窗口不加入interestingwindow的统计,现有的 w != mStartingWindow可能拦不住这个,有可能mStaringWindow跟w不是同一个实例,就可能导致StartingWindow类型的窗口被统计进去

添加启动窗口类型判断w.mAttrs.type != TYPE_APPLICATION_STARTING

4、wmshell crash导致黑屏
根据堆栈打印信息,通过try/catch捕获异常即可。

Android T startingwindow流程梳理相关推荐

  1. Android 11 截图流程梳理

    Android 原生截屏方式为,power键和音量下键的组合键,那么想要分析截屏流程就从按键的处理流程开始往下进行分析 1. PhoneWindowManager -- Android按键分发 pub ...

  2. android p wifi一直在扫描_(一百六十八)Android P wifi 扫描失败结果上报流程梳理-扫描上报梳理②...

    接(一百五十五)Android P wifi 扫描失败结果上报流程梳理-扫描上报梳理 扫描失败上报梳理发现梳理的差了很多,特补充 1.WificondScannerImpl @Override pub ...

  3. android wifi连接流程,(九十三) Android O 连接WiFi AP流程梳理续——保存网络-Go语言中文社区...

    前言: 之前在(五十五)Android O 连接WiFi AP流程梳理 梳理连接流程梳理到SupplicantStaNetworkHal 然后没梳理的下去,现在继续梳理下. 之前梳理的时序图 1.流程 ...

  4. (九十三) Android O 连接WiFi AP流程梳理续——保存网络

    前言: 之前在(五十五)Android O 连接WiFi AP流程梳理 梳理连接流程梳理到SupplicantStaNetworkHal 然后没梳理的下去,现在继续梳理下. 之前梳理的时序图 1.流程 ...

  5. (五十四)Android O WiFi 获取扫描结果流程梳理

    前言:之前在(五十) Android O WiFi的扫描流程梳理 已经梳理过扫描流程了,那扫描完的结果会呈现在设置的WiFi界面,那扫描结果是如何获取的呢? 1. wifi扫描结果简介 WiFi的扫描 ...

  6. (四十四)Android O WiFi启动流程梳理

    前言:最近又重新拿起来WiFi模块,从WiFi 各个流程梳理开始复习一下. 参考博客:https://blog.csdn.net/csdn_of_coder/article/details/51541 ...

  7. (九十七)Android O WiFi热点 开启流程梳理续(二)

    前言:从之前WiFi的连接流程可以知道WiFi最后一步会和服务端进行dhcp以获取ip地址,那么WiFi热点开启的时候应该也要配置相关dhcp属性,以供后续WiFi连接的时候ip分配,根据这块流程继续 ...

  8. (一百四十四)Android P WiFi 上网校验流程梳理

    前言:本文采用倒叙梳理,之前梳理流程没记下来忘了,现在再来一遍,所以说梳理什么的还是做个备忘比较好. 1.ConnectivityService 看网络校验相关log经常能看到如下log打印 log( ...

  9. (七十一)Android O WiFi热点 开启流程梳理

    前言:之前主要梳理了WiFi开启扫描连接的流程,现在梳理下WiFi 热点 的开启流程. 时序图mdj样式:https://download.csdn.net/download/sinat_200594 ...

最新文章

  1. x86标志位符号表示(PF奇偶位)
  2. 安卓手机反应慢又卡怎么办_手机卡顿反应慢怎么解决?
  3. 米家小白增强固件_中考体育:男1000米/女800米想拿满分,掌握呼吸法是关键
  4. 报表软件公司悬赏 BUG,100块钱1个的真实用意
  5. 计算机视觉领域最好用的开源图像标注工具
  6. 跟屌丝大哥学DB2-第四课 数据类型 ,表 ,视图,索引,模式,约束(一)
  7. Android APK系列5-------修改APK中的内容
  8. centos7 中彻底卸载mysql
  9. UML大作业【小型超市管理系统】
  10. 2020-08-31 ubuntu18.04下安装gitlab,以及使用邮箱注册
  11. 怎样用python制表_用Python绘图和制表(附泰坦尼克号案例)
  12. 2020牛客暑期多校训练营Decrement on the Tree(图论,set)
  13. VIVO内置应用卸载指南(IQOO NEO5为例)
  14. 如何书写md格式的文档
  15. AI 入行那些事儿(13)人工智能的三类技术岗位
  16. sql小数转换为百分数_这么齐全的数学单位换算表?寒假赶紧存下为孩子考试助力!...
  17. 新能源汽车数据库-分类型/地区/级别月度销量2015-2021进出口数据
  18. 循环冗余校验码CRC原理和实例
  19. Clustering by Passing Messages Between Data Points(Brendan J.Frey* and Delbert Dueck)例子
  20. 免费数据 | CnOpenData空气质量站点监测数据

热门文章

  1. 艺龙(elong),一个令人伤心的网站
  2. 异业联盟的案例分析,引流成功的方案
  3. jQuery中的serialize()和serializeArray()区别
  4. SpringBoot - 使用Assert校验让业务代码更简洁
  5. PathFileExists用法--使用#include
  6. 十、结构型模式——装饰者模式
  7. JS 的新一代日期/时间 API Temporal和 Moment.js的继承者
  8. 5G谈“风暴”可能为之尚早,芯片厂商之间的拉锯战才是这场变革的热身赛
  9. 网页版愤怒的小鸟Angry Birds
  10. 异步FIFO_Verilog实现