在Android手机上点击桌面icon时,系统会优先创建应用的StartingWindow,当StartingWindow成功创建后会开始开屏动画,等应用第一帧绘制完成后再移除StartingWindow。从这个流畅上是后添加的应用页面,为什么StaringWindow是在应用页面的顶部呢?下面通过S的源码对其进行分析。
一.添加StartingWindow
以热启动为例,添加StartingWindow的代码在TaskSnapshotWindow类中处理
static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
TaskSnapshot snapshot, ShellExecutor splashScreenExecutor,
@NonNull Runnable clearWindowHandler) {
final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
final int taskId = runningTaskInfo.taskId;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"create taskSnapshot surface for task: %d", taskId);
....
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
final int appearance = attrs.insetsFlags.appearance;
final int windowFlags = attrs.flags;
final int windowPrivateFlags = attrs.privateFlags;
layoutParams.packageName = mainWindowParams.packageName;
layoutParams.windowAnimations = mainWindowParams.windowAnimations;
layoutParams.dimAmount = mainWindowParams.dimAmount;
layoutParams.type = TYPE_APPLICATION_STARTING;
layoutParams.format = snapshot.getHardwareBuffer().getFormat();
layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
| FLAG_NOT_FOCUSABLE
| FLAG_NOT_TOUCHABLE;
// Setting as trusted overlay to let touches pass through. This is safe because this
// window is controlled by the system.
layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
| PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
layoutParams.token = appToken;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
...
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (res < 0) {
Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
return null;
}
session.relayout( window, layoutParams, -1, -1, View.VISIBLE, 0,
tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
tmpControls, new Bundle());
重点是
layoutParams.type = TYPE_APPLICATION_STARTING;
layoutParams.token = appToken;
其中这个appToken是待启动的ActivityRecord,应用添加页面时是同一个token。
接着分析session.addToDisplay方法,该方法会调用到WMS#addWindow方法
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
...
win.mToken.addWindow(win);
...
}
其中win是WindowState,win.mToken是ActivityRecord。下面是ActivityRecord的addWindow方法
@Override
void addWindow(WindowState w) {
super .addWindow(w);
boolean gotReplacementWindow = false;
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState candidate = mChildren.get(i);
gotReplacementWindow |= candidate.setReplacementWindowIfNeeded(w);
}
// if we got a replacement window, reset the timeout to give drawing more time
if (gotReplacementWindow) {
mWmService.scheduleWindowReplacementTimeouts(this);
}
checkKeyguardFlagsChanged();
}
主要是调用父类的addWindow方法,父类是WindowToken
void addWindow(final WindowState win) {
ProtoLog.d(WM_DEBUG_FOCUS,
"addWindow: win=%s Callers=%s", win, Debug.getCallers(5));
if (win.isChildWindow()) {
// Child windows are added to their parent windows.
return;
}
// This token is created from WindowContext and the client requests to addView now, create a
// surface for this token.
if (mSurfaceControl == null) {
createSurfaceControl(true /* force */);
// Layers could have been assigned before the surface was created, update them again
reassignLayer(getSyncTransaction());
}
if (!mChildren.contains(win)) {
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", win, this);
addChild(win, mWindowComparator);
mWmService.mWindowsChanged = true;
// TODO: Should we also be setting layout needed here and other places?
}
}
重点是addChild(win, mWindowComparator); 这行代码,其中mWindowComparator是
private final Comparator<WindowState> mWindowComparator =
(WindowState newWindow, WindowState existingWindow) -> {
final WindowToken token = WindowToken.this;
if (newWindow.mToken != token) {
throw new IllegalArgumentException("newWindow=" + newWindow
+ " is not a child of token=" + token);
}
if (existingWindow.mToken != token) {
throw new IllegalArgumentException("existingWindow=" + existingWindow
+ " is not a child of token=" + token);
}
return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : - 1 ;
};
/**
* Returns true if the new child window we are adding to this token is considered greater than
* the existing child window in this token in terms of z-order.
*/
@Override
protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
WindowState existingWindow) {
final int type1 = newWindow.mAttrs.type;
final int type2 = existingWindow.mAttrs.type;
// Base application windows should be z-ordered BELOW all other windows in the app token.
if (type1 == TYPE_BASE_APPLICATION && type2 != TYPE_BASE_APPLICATION) {
return false ;
} else if (type1 != TYPE_BASE_APPLICATION && type2 == TYPE_BASE_APPLICATION) {
return true;
}
// Starting windows should be z-ordered ABOVE all other windows in the app token.
if (type1 == TYPE_APPLICATION_STARTING && type2 != TYPE_APPLICATION_STARTING) {
return true;
} else if (type1 != TYPE_APPLICATION_STARTING && type2 == TYPE_APPLICATION_STARTING) {
return false;
}
// Otherwise the new window is greater than the existing window.
return true;
}
结合以上代码,existingWindow是TYPE_APPLICATION_STARTING,而newWindow是TYPE_BASE_APPLICATION,这种情况返回的是false。
接着看WindowContainer如何处理addChild方法。
@CallSuper
protected void addChild(E child, Comparator<E> comparator) {
if (!child.mReparenting && child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
+ " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
int positionToAdd = -1;
if (comparator != null) {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (comparator.compare(child, mChildren.get(i)) < 0) {
positionToAdd = i;
break;
}
}
}
if (positionToAdd == -1) {
mChildren.add(child);
} else {
mChildren.add(positionToAdd, child);
}
// Set the parent after we've actually added a child in case a subclass depends on this.
child.setParent(this);
}
主要看if判断
if (comparator.compare(child, mChildren.get(i)) < 0) {
positionToAdd = i;
break;
}
mChildren里面只有一个StartingWindow,child是应用windowState,mChildren.get(i)是StartingWIndow。该if判断返回true,再执行mChildren.add(0, child);将新页面添加在第一位。这样顶部显示的就是StartingWindow了。