WMS之启动窗口

227 阅读4分钟

本文详细解析 Android 窗口管理服务(WMS)在 Activity 启动时显示启动窗口(StartingWindow)的机制,涵盖从窗口创建到移除的完整流程,结合源码阐述关键逻辑与线程协作。

一、StartingWindow 的作用:平滑过渡的 “临时画布”

当 Activity 启动时,若应用进程尚未创建或窗口未就绪,系统会先显示一个StartingWindow作为预览界面(通常为应用图标或主题背景),避免黑屏,提升用户体验。其核心流程包括:

  1. 启动阶段:AMS 通知 WMS 创建启动窗口,显示应用图标或主题背景。
  2. 过渡阶段:当真实 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,记录启动窗口数据(startingDatastartingView)。
  • 作用:关联启动窗口与目标 Activity,确保移除时正确匹配。

2. 线程与 Handler 通信

  • android.display 线程:负责 WMS 核心逻辑,处理窗口布局、动画和 Surface 渲染。
  • 跨线程同步:通过Handler.runWithScissors和消息队列(如H.ADD_STARTINGH.FINISHED_STARTING)确保操作在正确线程执行,避免并发问题。

3. 窗口类型与层级

  • TYPE_APPLICATION_STARTING:特殊窗口类型,确保启动窗口在普通窗口之下、壁纸之上,避免遮挡系统元素。
  • Z 轴层级:启动窗口的层级低于真实 Activity 窗口,确保过渡完成后自然覆盖。

五、典型场景与设计考量

场景 1:应用进程冷启动

  1. 用户点击应用图标,AMS 发现进程未运行,通知 WMS 显示启动窗口。
  2. WMS 创建启动窗口(显示应用图标 + 背景),同时启动应用进程。
  3. 进程启动后,创建真实 Activity 窗口,WMS 布局时移除启动窗口,显示真实界面。

场景 2:Activity 切换时的过渡

  • 若上一个 Activity 仍有启动窗口未移除,WMS 会将其转移给新 Activity,避免重复创建(transferFrom逻辑),提升效率。

设计目标

  • 无感知过渡:启动窗口与真实窗口的切换通过动画衔接,用户无法察觉中间过程。
  • 资源优化:启动窗口使用轻量级视图,避免创建复杂布局,减少内存占用。

六、总结:启动窗口的生命周期图谱

plaintext

用户点击App图标 → AMS启动Activity → AMS通知WMS显示启动窗口
    ↓                          ↓
  创建StartingData           创建PhoneWindow(不可触摸)
    ↓                          ↓
android.display线程处理       添加到WindowManager
    ↓                          ↓
  布局渲染(Surface)         显示预览界面(应用图标)
    ↓                          ↓
真实Activity窗口就绪          触发移除逻辑
    ↓                          ↓
WMS移除启动窗口              显示真实界面

核心要点

  1. 线程协作:通过Handler在 android.display 线程执行关键操作,确保线程安全。

  2. 临时属性:启动窗口不可交互,仅用于视觉过渡,完成后立即移除。

  3. 层级管理:利用窗口类型和 Z 轴排序,确保启动窗口与真实窗口的正确叠加顺序。

理解 StartingWindow 机制有助于优化应用启动速度和用户体验,例如通过自定义主题缩短启动窗口显示时间,或避免不必要的窗口层级冲突。