通过Token和真正的界面连接

系统之所以能把 StartWindow 和应用主窗口关联起来,是因为:
	•	两者都绑定了同一个 WindowToken;
	•	App 的 Activity 还未真正 attach DecorView 之前,系统先用这个 token 显示了 StartWindow;
	•	App 在 resume 时,通过相同的 token 注册正式窗口,系统完成替换。   具体些

这个过程确实是通过 相同的 WindowToken 将 StartWindow 和应用真正的界面连接起来的。下面我给出更详细的时序与代码链路说明,解释系统是如何通过 WindowToken 实现 StartWindow 到主窗口的替换过程:

🔧 一、什么是 StartWindow?

StartWindow 本质上是 系统在启动 Activity 时,为了减少启动白屏而先行添加的一个临时窗口。它通常包含应用图标和背景色,是由系统进程(SystemServer → ActivityTaskManagerService → WindowManagerService)添加的,而不是应用自身添加的。

二、关联机制核心:同一个 WindowToken

StartWindow 和 App 的真正窗口都使用同一个 WindowToken(类型为 AppWindowToken),这使得系统能在应用窗口准备好时,无缝替换掉 StartWindow

三、详细流程和关键源码

1. 启动 Activity → 创建 WindowToken

在 ActivityTaskManagerService 中:

boolean startActivity(...) {
    ...
    // 创建 ActivityRecord,它会包含一个 AppWindowToken
    mRootWindowContainer.startActivityLocked(...);
}

// ActivityRecord.java
void createWindowToken() {
    mWmService.mRoot.createWindowToken(appToken.token, ...);
}

此时,WindowManagerService 会先注册一个 AppWindowToken 到 WMS 中,并记录下来。

2. 添加 StartWindow(StartingWindow)

在 Activity 启动早期,WMS 会尝试为该 ActivityRecord 添加一个 Starting Window:

// ActivityRecord.java
boolean createStartingWindow(...) {
    mWmService.mStartingSurfaceController.addStartingWindow(this);
}

然后由 StartingSurfaceDrawer 进行实际绘制,它最终也是调用 WindowManagerService 的 addWindow() 添加窗口。

因为此时应用进程还没 attach,系统就根据 ActivityRecord 提前把这个窗口 token 绑定到 StartWindow 上

3. 应用进程启动并 attach ApplicationThread

// ActivityThread.java
public void handleLaunchActivity(...) {
    ...
    // 创建 PhoneWindow 并设置 token
    Activity activity = performLaunchActivity(...);
}



Activity activity = Instrumentation.newActivity(...);
activity.attach(...); // 内部会调用 setToken()

这里传进来的 token 正是 WMS 创建的 WindowToken。

// Activity.java
public void attach(...) {
    mWindow = new PhoneWindow(this, ...);
    mWindow.setWindowManager(..., token, ...);
}

所以:App 主窗口的 Window(PhoneWindow)会和 StartWindow 共用一个 token

4. 应用调用 setContentView(),并添加窗口

// Activity.java
setContentView() -> WindowManager.addView(DecorView)



// WindowManagerGlobal.java
mWindowManager.addView(mView, params)

最终调用:

// Session.java
public int add(...) {
    return mService.addWindow(...); // IPC 调用 WMS
}

WMS 检查到:这个窗口的 token 已存在,并且之前添加了 StartingWindow,就会在此时移除 StartWindow 并替换为正式窗口

// WindowManagerService.java
public int addWindow(...) {
    ...
    // 检查 token,如果已有 starting window,就移除
    if (token.hasStartingWindow) {
        removeStartingWindow(token);
    }
    // 添加应用真正窗口
    newWindow = new WindowState(...);
    ...
}

总结

阶段 说明
1️⃣ 创建 ActivityRecord 包含 AppWindowToken,系统分配一个唯一 IBinder
2️⃣ 添加 StartWindow 系统用 AppWindowToken 添加临时窗口(Splash)
3️⃣ 应用进程 attach 通过同一个 token 创建 PhoneWindow,并 addView
4️⃣ 正式窗口添加 WMS 识别相同 token,移除 StartWindow,替换主窗口

这个机制保证了系统与应用窗口之间的切换是无感的,看起来就像一个窗口从启动画面平滑切换到了内容页面

从更深层次讲清楚 StartWindow 到主窗口的切换机制 —— 包括每个组件的角色、关键类之间的调用关系,及背后跨进程的协作方式:

四、涉及的关键组件关系图(简述)

        +-----------------------------+
        |     ActivityTaskManager    | ← 管理 Activity 生命周期
        +-----------------------------+
                    |
                    v
        +-----------------------------+
        |      ActivityRecord         | ← 包含 AppWindowToken(IBinder)
        +-----------------------------+
                    |
                    v
        +-----------------------------+
        |     WindowManagerService    | ← 控制窗口添加/替换
        +-----------------------------+
                    |
       (addWindow, removeStartingWindow)
                    |
                    v
        +-----------------------------+
        |     AppWindowToken          | ← 共享给 StartWindow 和主窗口
        +-----------------------------+

五、主窗口替换 StartWindow 的时机与条件

替换逻辑触发点:WindowManagerService.addWindow()

当应用调用 WindowManager.addView() 添加 DecorView 时,通过 Binder 进入 WindowManagerService.addWindow()。

// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int addWindow(Session session, IWindow client, ...) {
    ...
    AppWindowToken atoken = token.asAppWindowToken();
    ...
    // Check if this token has a starting window
    if (atoken.startingWindow != null) {
        removeStartingWindow(atoken);
    }
    ...
}

** 替换的先决条件:**

1. AppWindowToken 已存在,并关联了 StartWindow;

2. 应用通过相同 token 添加了一个主窗口(例如 TYPE_BASE_APPLICATION);

3. 此主窗口通过 addView() 的方式进入 WMS,WMS 会在 addWindow() 中做替换逻辑;

六、removeStartingWindow 的内部操作

void removeStartingWindow(AppWindowToken token) {
    if (token.startingWindow != null) {
        StartingData data = token.startingData;
        SurfaceControl leash = data.leash; // StartWindow 的 surface leash
        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
        t.remove(leash);
        t.apply();
        token.startingWindow = null;
        token.startingData = null;
    }
}

简化来说:

• 会通过 SurfaceControl.Transaction 移除 StartWindow 的 Surface;

• 然后释放掉与 StartWindow 相关的所有数据;

七、重点剖析:为何两者能无缝替换?

核心:AppWindowToken 是 SurfaceFlinger 中的 layer 容器

• 每一个 WindowState(无论是 StartWindow 还是 Activity 主窗口)在 SurfaceFlinger 中都有一个 SurfaceControl;

• 这些控件都挂载在同一个 AppWindowToken 的 Layer Tree 下;

• 所以:

StartWindow 添加时,Layer 就存在;

正式窗口添加时,直接复用同一个 Layer Token,层级相同,SurfaceFlinger 无需重建大结构,替换就非常高效

八、StartWindow 替换主窗口的用户可见流程:

时间点 屏幕展示 内部状态
0ms 启动图(Splash) StartWindow 已添加
300ms 应用进程 attach,开始构建 DecorView StartWindow 仍在展示
400ms App 调用 setContentView() 并调用 addView() 启动正式窗口流程
450ms WMS 收到 addWindow 请求,发现已有 StartWindow 调用 removeStartingWindow()
470ms DecorView 添加完成,开始绘制内容 主窗口接管屏幕

九、为什么使用同一个 WindowToken 很关键?

因为 WMS 的窗口层级是通过 Token 来组织的:

• 相同 WindowToken → 同一个 AppWindowToken → 同一个 Layer 容器;

• 所以系统能保证:

• StartWindow 和主窗口位置、Z-order 完全一致

• 替换时无卡顿、无跳变、无视图重建。

十、你可以通过 adb 验证:

adb shell dumpsys window | grep "Starting"

可看到:

Window #1 Window{1234 Starting com.example.app}:
  mToken=AppWindowToken{...}

然后应用主窗口:

Window #2 Window{5678 u0 com.example.app/com.example.app.MainActivity}:
  mToken=AppWindowToken{...} ← same token

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器