窗口显示与隐藏策略:可见性计算、过渡动画与超时处理

窗口的显示与隐藏,说白了就是一场精心编排的「舞台剧」。每个窗口什么时候登场、什么时候退场、退场时要不要来个华丽的转身——这些都由 WindowManagerService 来调度。今天我就带大家深入看看这套机制的核心逻辑。

一、窗口可见性计算:谁该出现在屏幕上?

窗口可见性不是简单的「显示/隐藏」二选一。它是一套层层递进的判断逻辑。我个人习惯把可见性分为三个层级:

  • 全局可见性:窗口是否被完全遮挡?比如被另一个全屏窗口盖住。
  • 局部可见性:窗口是否部分可见?比如分屏模式下。
  • 动画可见性:窗口正在执行过渡动画时,它的可见状态怎么算?

WMS 里有个核心方法叫 computeVisibility,它干的事就是遍历所有窗口,算出每个窗口的最终可见状态。嗯,这里要注意,计算顺序很重要——Z-order 高的窗口会覆盖低的。

核心逻辑:窗口可见性 = 窗口自身可见标志 && 父容器可见 && 未被完全遮挡 && 未处于隐藏动画中

我在项目中遇到过一个问题:一个 Dialog 窗口明明调用了 show(),但就是显示不出来。查了半天发现是它的宿主 Activity 窗口被标记为 paused 状态,导致子窗口的可见性被连带否决了。所以你看,可见性计算是个连锁反应。

二、AppTransition 过渡动画:窗口切换的「润滑剂」

窗口切换如果直接闪现,用户体验会很生硬。Android 用 AppTransition 机制来管理这些过渡动画。说白了,它就是一套动画编排引擎。

AppTransition 的核心工作流程是这样的:

  1. WMS 收到窗口显示/隐藏请求
  2. AppTransition 根据窗口类型和请求类型,选择合适的动画
  3. 动画参数被封装成 Transition 对象
  4. SurfaceFlinger 根据这些参数渲染每一帧

你想想看,为什么有些 App 的启动动画特别流畅,有些却卡顿?关键就在 AppTransition 的配置上。系统预置了几种过渡模式:

过渡类型 触发场景 动画效果
OPEN 启动新 Activity 从右向左滑入
CLOSE 关闭 Activity 从左向右滑出
TO_FRONT 将窗口提到前台 缩放+淡入
TO_BACK 将窗口推到后台 缩放+淡出

我曾经调试过一个诡异的问题:某个 App 的启动动画总是「闪一下」才显示。后来发现是 AppTransition 的动画时长被某个第三方库改成了 0ms。嗯,这种坑往往藏得很深。

小技巧:如果你想自定义窗口过渡动画,可以在 ActivityOptions 中指定自定义的动画资源。但要注意,动画时长不要超过 500ms,否则用户会觉得卡顿。

三、窗口显示超时处理:别让用户等太久

窗口显示不是瞬间完成的。从 WMS 发出显示指令,到 SurfaceFlinger 真正把画面渲染出来,中间有延迟。如果这个延迟太长,用户就会看到黑屏或者白屏。

Android 为此设计了超时机制。WMS 会为每个窗口显示请求启动一个定时器,默认超时是 5 秒。如果超时了还没显示出来,WMS 会怎么做?

  • 记录一条 ANR 日志,标记为 WINDOW_TIMEOUT
  • 强制将窗口状态设置为 DRAW_DONE
  • 跳过动画,直接显示窗口内容(哪怕内容还没准备好)

为什么会超时?我总结了几种常见原因:

  • SurfaceFlinger 繁忙:GPU 渲染任务堆积,来不及处理新窗口
  • 窗口内容过大:比如一个 4K 分辨率的窗口,解码和上传纹理需要时间
  • 死锁或阻塞:WMS 主线程被某个操作卡住,无法及时处理显示请求

避坑指南:我曾经遇到一个 case,某个 App 在低端机上总是启动黑屏 3-4 秒。原因是它的启动窗口加载了一张 10MB 的图片。解决方案是把图片压缩到 1MB 以内,同时使用 Window.setBackgroundDrawable() 先显示一个占位背景。

四、知识体系总览

下面这张图把窗口显示与隐藏的核心流程串起来了。你可以看到,可见性计算是入口,AppTransition 是中间层,超时处理是兜底机制。三者缺一不可。

窗口显示与隐藏核心流程 窗口显示/隐藏请求 窗口可见性计算 全局可见性 → 局部可见性 → 动画可见性 AppTransition 过渡动画 OPEN / CLOSE / TO_FRONT / TO_BACK SurfaceFlinger 渲染 超时处理(5秒)

从这张图可以看得很清楚:正常流程是请求→可见性计算→动画→渲染。但如果渲染超时,就会走虚线分支,强制结束等待。这个兜底机制虽然粗暴,但至少保证了用户不会一直盯着黑屏。

个人经验:调试窗口显示问题时,我建议先看 dumpsys window 的输出,重点关注 mAppTransition 的状态和 mLastWindowTimeout 的时间戳。这两个字段能帮你快速定位是动画卡住了,还是渲染超时了。

好了,窗口显示与隐藏策略的核心内容就这些。记住一句话:可见性计算是「能不能显示」,AppTransition 是「怎么显示」,超时处理是「显示不了怎么办」。三者配合,才能让窗口切换如丝般顺滑。

公众号:蓝海资料掘金营,微信deep3321