Android T StartingWindow 源码解析与实现机制
当我们在Android设备上点击一个应用图标时,系统会立即展示一个过渡界面,这个界面就是StartingWindow(启动窗口)。作为Android系统窗口管理的重要组成部分,StartingWindow机制在提升用户体验方面发挥着关键作用。本文将深入剖析Android T版本中StartingWindow的实现原理,从系统架构到具体实现细节,为开发者提供全面的技术解析。
1. StartingWindow 核心概念与设计初衷
StartingWindow是Android系统在应用Activity真正显示前展示的过渡窗口,主要用于解决以下几个核心问题:
- 视觉连续性:在Activity完成布局加载和绘制前提供平滑的视觉过渡
- 性能感知优化:掩盖进程创建、资源加载等耗时操作带来的延迟感
- 品牌展示:允许应用通过主题定制展示品牌标识和启动画面
在Android T中,StartingWindow的实现涉及多个系统组件的协作:
SystemServer进程(AMS/WMS) ↔ Binder通信 ↔ WMShell进程(SystemUI)这种跨进程设计将窗口管理的核心逻辑与具体实现分离,提高了系统的模块化和可维护性。
StartingWindow的三种类型:
| 类型 | 常量值 | 使用场景 | 特点 |
|---|---|---|---|
| 无启动窗口 | STARTING_WINDOW_TYPE_NONE | 应用内Activity切换 | 不显示任何过渡窗口 |
| 快照启动窗口 | STARTING_WINDOW_TYPE_SNAPSHOT | 任务切换到前台 | 使用最近任务快照 |
| 闪屏启动窗口 | STARTING_WINDOW_TYPE_SPLASH_SCREEN | 应用冷启动 | 基于应用主题的空白窗口 |
2. StartingWindow 创建流程深度解析
StartingWindow的创建始于Activity的启动请求,整个流程跨越多个系统组件。以下是关键步骤的详细分析:
2.1 启动入口与类型决策
创建流程的起点是ActivityStarter.startActivityLocked(),经过以下调用链:
ActivityStarter.startActivityLocked() → Task.startActivityLocked() → ActivityRecord.showStartingWindow() → ActivityRecord.addStartingWindow()类型决策的核心逻辑位于ActivityRecord.getStartingWindowType(),该方法基于多个参数确定启动窗口类型:
private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean activityAllDrawn, TaskSnapshot snapshot) { // 特殊场景处理:任务启动器(trampoline activity) if (!newTask && taskSwitch && processRunning && !activityCreated && task.intent != null && mActivityComponent.equals(task.intent.getComponent())) { if (topAttached != null && topAttached.isSnapshotCompatible(snapshot)) { return STARTING_WINDOW_TYPE_SNAPSHOT; } return STARTING_WINDOW_TYPE_NONE; } // 常规决策逻辑 if ((newTask || !processRunning || (taskSwitch && !activityCreated)) && !isActivityTypeHome()) { return STARTING_WINDOW_TYPE_SPLASH_SCREEN; } if (taskSwitch && allowTaskSnapshot) { if (isSnapshotCompatible(snapshot)) { return STARTING_WINDOW_TYPE_SNAPSHOT; } if (!isActivityTypeHome()) { return STARTING_WINDOW_TYPE_SPLASH_SCREEN; } } return STARTING_WINDOW_TYPE_NONE; }2.2 跨进程协作实现
确定启动窗口类型后,系统通过以下流程完成实际创建:
SystemServer侧准备数据:
- 创建
StartingData子类实例(SplashScreenStartingData或SnapshotStartingData) - 通过
TaskOrganizerController建立与WMShell的通信通道
- 创建
WMShell侧创建窗口:
StartingWindowController接收创建请求StartingSurfaceDrawer根据类型执行具体创建逻辑- 对于SplashScreen类型,构建ContentView并添加窗口
- 对于Snapshot类型,直接使用任务快照创建窗口
关键数据结构对比:
| 数据结构 | 所在进程 | 职责 |
|---|---|---|
| StartingData | SystemServer | 抽象启动窗口数据模型 |
| StartingSurfaceController | SystemServer | 控制系统与WMShell的交互 |
| StartingWindowController | WMShell | 启动窗口操作入口 |
| StartingSurfaceDrawer | WMShell | 具体实现窗口创建/移除 |
3. StartingWindow 移除机制分析
StartingWindow的移除与Activity的绘制状态密切相关,主要触发点是主窗口完成绘制。以下是移除流程的关键节点:
3.1 移除触发路径
应用绘制完成通知:
ViewRootImpl.reportDrawFinished() → WindowSession.finishDrawing() → WMS.finishDrawingWindow()窗口系统处理:
WMS.requestTraversal() → WindowSurfacePlacer.performSurfacePlacement() → RootWindowContainer.applySurfaceChangesTransaction()启动窗口移除决策:
WindowState.performShowLocked() → ActivityRecord.onFirstWindowDrawn() → ActivityRecord.removeStartingWindow()
3.2 移除动画处理
Android T引入了启动窗口退出动画机制,其实现依赖于应用是否设置了退出动画监听器:
// 应用侧设置退出动画 splashScreen.setOnExitAnimationListener { view -> // 自定义退出动画 view.animate().alpha(0f).setDuration(300).start() }系统处理流程如下:
动画触发:
ActivityRecord.removeStartingWindow() → transferSplashScreenIfNeeded() → requestCopySplashScreen()跨进程协作:
- WMShell复制SplashScreen视图
- 通过
ActivityTaskManagerService通知应用进程 - 应用接收视图并执行动画
最终移除: 动画完成后,系统调用
removeStartingWindowAnimation()完成窗口移除。
4. 关键实现细节与优化技巧
4.1 性能优化设计
异步创建机制: StartingWindow的创建通过
AddStartingWindow异步任务执行,避免阻塞主线程:private class AddStartingWindow implements Runnable { @Override public void run() { // 异步执行窗口创建 StartingSurface surface = startingData.createStartingSurface(); // 处理结果... } }资源复用策略:
- 相同Activity的连续启动会复用已有StartingWindow
- 快照类型窗口直接复用任务快照,减少资源创建开销
4.2 常见问题解决方案
问题1:温启动白屏
当应用保活机制导致Welcome Activity被跳过时,可能出现白屏。解决方案是在getStartingWindowType()中添加特殊处理:
if (isWarmStart && snapshot != null) { return STARTING_WINDOW_TYPE_NONE; }问题2:启动图标定制
修改SplashScreen的图标显示逻辑:
// 在SplashscreenContentDrawer.makeSplashScreenContentView中 if (attrs.mSplashScreenIcon == null) { attrs.mSplashScreenIcon = new ColorDrawable(Color.TRANSPARENT); }问题3:窗口统计异常
修正ActivityRecord中的窗口统计逻辑:
boolean isInterestingWindow(WindowState w) { return w.mAttrs.type != TYPE_APPLICATION_STARTING && w.isVisibleNow(); }5. 调试与定制开发实践
5.1 调试技巧
日志过滤:
adb logcat -s WindowManager | grep "StartingWindow"关键属性检查:
adb shell dumpsys window windows | grep -A 10 "StartingWindow"WMShell调试:
adb shell dumpsys activity service SystemUIService WMShell
5.2 定制开发指南
编译与部署:
# 系统侧修改 make framework adb push framework.jar /system/framework/ # WMShell侧修改 make SystemUI adb push SystemUI.apk /system_ext/priv-app/SystemUI/主题定制示例:
<style name="AppStartingTheme" parent="Android:Theme.Light.NoTitleBar"> <item name="android:windowBackground">@drawable/app_starting_bg</item> <item name="android:windowSplashScreenAnimatedIcon">@drawable/app_icon</item> <item name="android:windowSplashScreenAnimationDuration">300</item> </style>在实际项目中修改StartingWindow行为时,需要特别注意跨进程交互的同步问题,以及窗口Z-order的管理。通过合理利用现有扩展点,可以在不修改系统核心代码的情况下实现大多数定制需求。