StartingWindow为什么在最上层

217 阅读3分钟

在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了。