本文详细解析 Android 窗口管理服务(WMS)在 Activity 启动时显示启动窗口(StartingWindow)的机制,涵盖从窗口创建到移除的完整流程,结合源码阐述关键逻辑与线程协作。
一、StartingWindow 的作用:平滑过渡的 “临时画布”
当 Activity 启动时,若应用进程尚未创建或窗口未就绪,系统会先显示一个StartingWindow作为预览界面(通常为应用图标或主题背景),避免黑屏,提升用户体验。其核心流程包括:
- 启动阶段:AMS 通知 WMS 创建启动窗口,显示应用图标或主题背景。
- 过渡阶段:当真实 Activity 窗口准备就绪后,启动窗口逐渐消失,显示真实界面。
二、启动流程:从 AMS 到 WMS 的跨进程协作
1. AMS 触发启动窗口显示
在 Activity 启动流程中(如AMS.startActivityLocked),若应用进程未运行,标记需显示启动窗口:
java
// ActivityStack.java
if (proc == null || proc.thread == null) {
showStartingIcon = true; // 进程未启动,显示启动窗口
}
mWindowManager.setAppStartingWindow(...); // 通知WMS创建启动窗口
2. WMS 创建启动窗口
WMS 的setAppStartingWindow方法负责创建启动窗口数据,并通过 Handler 发送消息到android.display 线程处理:
java
// WindowManagerService.java
wtoken.startingData = new StartingData(...); // 封装启动窗口属性(主题、图标等)
mH.sendMessageAtFrontOfQueue(Message.obtain(mH, H.ADD_STARTING, wtoken)); // 发送消息到android.display线程
3. 线程协作:在 android.display 线程构建窗口
通过Handler.runWithScissors确保在指定线程执行窗口创建逻辑:
java
// WindowManagerService.H.handleMessage
case ADD_STARTING: {
View view = mPolicy.addStartingWindow(...); // mPolicy为PhoneWindowManager
if (view != null) {
wtoken.startingView = view; // 保存启动窗口视图
}
}
4. PhoneWindowManager 构建启动窗口
创建一个临时窗口(类型为TYPE_APPLICATION_STARTING),设置不可触摸和聚焦,仅用于显示预览:
java
// PhoneWindowManager.addStartingWindow
PhoneWindow win = new PhoneWindow(context);
win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
win.setFlags(FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE);
win.setLayout(MATCH_PARENT, MATCH_PARENT); // 填充全屏
WindowManager wm = context.getSystemService(WINDOW_SERVICE);
wm.addView(win.getDecorView(), params); // 添加到窗口管理器
三、窗口显示与布局:从 Surface 到屏幕渲染
1. 布局与表面处理
WMS 通过performLayoutAndPlaceSurfacesLocked触发窗口布局和 Surface 渲染,其中启动窗口作为临时 Surface 参与层级排序:
java
// WindowManagerService.performLayoutAndPlaceSurfacesLockedInner
for (WindowState w : windows) {
if (w.mAttrs.type == TYPE_APPLICATION_STARTING) {
// 处理启动窗口的Surface层级
winAnimator.setSurfaceBoundariesLocked(...);
}
}
2. 真实窗口就绪:移除启动窗口
当 Activity 的真实窗口准备好显示时(WindowStateAnimator.commitFinishDrawingLocked),触发启动窗口移除逻辑:
java
// WindowStateAnimator.performShowLocked
if (mWin.mAppToken.startingData != null) {
mService.mFinishedStarting.add(mWin.mAppToken);
mService.mH.sendEmptyMessage(H.FINISHED_STARTING); // 发送移除消息
}
3. 线程消息驱动移除
在 android.display 线程中,通过H.FINISHED_STARTING消息调用removeStartingWindow,从窗口管理器中移除临时窗口:
java
// WindowManagerService.H.handleMessage
case FINISHED_STARTING: {
mPolicy.removeStartingWindow(token, view); // 调用PhoneWindowManager移除视图
wm.removeView(window); // 从WindowManager中移除
}
四、关键类与机制解析
1. AppWindowToken:窗口令牌的生命周期
- 每个 Activity 对应一个
AppWindowToken,记录启动窗口数据(startingData、startingView)。 - 作用:关联启动窗口与目标 Activity,确保移除时正确匹配。
2. 线程与 Handler 通信
- android.display 线程:负责 WMS 核心逻辑,处理窗口布局、动画和 Surface 渲染。
- 跨线程同步:通过
Handler.runWithScissors和消息队列(如H.ADD_STARTING、H.FINISHED_STARTING)确保操作在正确线程执行,避免并发问题。
3. 窗口类型与层级
- TYPE_APPLICATION_STARTING:特殊窗口类型,确保启动窗口在普通窗口之下、壁纸之上,避免遮挡系统元素。
- Z 轴层级:启动窗口的层级低于真实 Activity 窗口,确保过渡完成后自然覆盖。
五、典型场景与设计考量
场景 1:应用进程冷启动
- 用户点击应用图标,AMS 发现进程未运行,通知 WMS 显示启动窗口。
- WMS 创建启动窗口(显示应用图标 + 背景),同时启动应用进程。
- 进程启动后,创建真实 Activity 窗口,WMS 布局时移除启动窗口,显示真实界面。
场景 2:Activity 切换时的过渡
- 若上一个 Activity 仍有启动窗口未移除,WMS 会将其转移给新 Activity,避免重复创建(
transferFrom逻辑),提升效率。
设计目标
- 无感知过渡:启动窗口与真实窗口的切换通过动画衔接,用户无法察觉中间过程。
- 资源优化:启动窗口使用轻量级视图,避免创建复杂布局,减少内存占用。
六、总结:启动窗口的生命周期图谱
plaintext
用户点击App图标 → AMS启动Activity → AMS通知WMS显示启动窗口
↓ ↓
创建StartingData 创建PhoneWindow(不可触摸)
↓ ↓
android.display线程处理 添加到WindowManager
↓ ↓
布局渲染(Surface) 显示预览界面(应用图标)
↓ ↓
真实Activity窗口就绪 触发移除逻辑
↓ ↓
WMS移除启动窗口 显示真实界面
核心要点:
-
线程协作:通过
Handler在 android.display 线程执行关键操作,确保线程安全。 -
临时属性:启动窗口不可交互,仅用于视觉过渡,完成后立即移除。
-
层级管理:利用窗口类型和 Z 轴排序,确保启动窗口与真实窗口的正确叠加顺序。
理解 StartingWindow 机制有助于优化应用启动速度和用户体验,例如通过自定义主题缩短启动窗口显示时间,或避免不必要的窗口层级冲突。