通过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