View 的绘制、刷新和合成横跨应用层、框架层、Native 层三套机制,任何一环理解错都会表现为掉帧。本文按一条阅读线串起来:先搞清楚一帧怎么画出来(ViewRootImpl + 绘制/刷新机制),再理解为什么掉帧(渲染机制),然后看 Surface 家族怎么绕开主线程,最后回到 View 的滑动实现。
一、ViewRootImpl 与绘制/刷新机制:measure/layout/draw 由谁驱动
ViewRootImpl 是 View 树与 WindowManager 的桥梁。setView 后持有 DecorView,负责 requestLayout 与 performTraversals(也就是 measure / layout / draw),再经 Surface 交合成。IWindowSession 把窗口属性同步给 WMS,输入事件从 InputStage 链进 View 树。
绘制分三步,由 Choreographer 的 VSYNC 回调驱动:measure 决定宽高,layout 决定位置,draw 把内容画到硬件缓冲。MeasureSpec 是父布局给的约束:EXACTLY、AT_MOST、UNSPECIFIED 决定 wrap 和 match 怎么算。刷新不是「调用即重绘」:Choreographer 驱动帧回调,TraversalRunnable 把三次遍历绑在同一 VSYNC 上;invalidate 只标脏,真正绘制在下一帧。requestLayout 重走测量和布局,invalidate 通常只重绘——这就是「改尺寸 requestLayout、改颜色 invalidate 就够」的原因。
三个容易踩的坑
- 子线程直接改 View:
ViewRootImpl会检查线程,非创建线程改 UI 直接抛异常;Dialog/Popup也各有自己的ViewRootImpl,别假设全局只有一棵树。 - invalidate 不等于重新排版:它只脏当前显示列表;改 margin 或孩子数量必须
requestLayout。 - 频繁 requestLayout / 在 draw 里分配对象:前者重走 measure/layout,后者触发 GC 抖动,都会掉帧。
什么时候用:自定义 View、卡顿排查和动画占位出问题时回到这套流程;普通业务改颜色用 invalidate 即可,分析掉帧用 Profile GPU 或 Perfetto。
二、渲染机制与掉帧排查:RenderThread、16ms 预算与优化
渲染从 View 树的 measure / layout / draw,到 RenderThread 把 DisplayList 交给 GPU。硬件加速后很多绘制变成 OpenGL / Vulkan 命令——理解这一点才能解释为什么某些 setLayerType 会变快或更慢。16ms 一帧是 60Hz 的预算,过度绘制、过深层级和主线程耗时都会掉帧。
优化先开 GPU 呈现模式看颜色,再压平 ConstraintLayout,减少 clip 和透明;列表用 RecyclerView 复用,动画放硬件层要有节制,Systrace 看帧边界。把测量和绘制从主线程长任务里拆开,比换旗舰机更能稳定帧率。
四个容易踩的坑
- 过度绘制:重叠的半透明/全屏背景反复绘制,GPU 呈现模式里一片红。
- 层级过深:measure/layout 随嵌套深度膨胀,优先压平
ConstraintLayout。 - 软件层和硬件层混用:会打断渲染管线,能硬件加速就不要强制软件层。
- 主线程长任务阻塞绘制:把测量和绘制从主线程长任务里拆开,比换机更管用。
什么时候用:卡顿、列表滑动掉帧、动画不跟手时回到渲染机制排查;纯业务逻辑问题别在这里找原因。
三、Surface 家族:SurfaceView 与 TextureView 怎么选
SurfaceView 拥有独立的 Surface,由 SurfaceFlinger 单独合成,不走普通 View 的绘制路径。游戏、相机、视频解码这类高帧率场景更适合它,因为可以绕开主线程绘制并尝试硬件 overlay。经典用法是实现 SurfaceHolder.Callback,在 surfaceCreated 启动渲染线程、surfaceDestroyed 立即停止。它默认位于窗口最底层,需要 setZOrderMediaOverlay 或 setZOrderOnTop 才能盖住其它控件;因为不在 View 树里,旋转、透明度动画会比较别扭。从 API 24 起支持 setChildWindow 嵌入,但仍要小心生命周期与 Activity 重建;能 overlay 时尽量让格式匹配显示设备,减少 GLES 合成。
TextureView 把内容画进 View 树里的硬件纹理,因此能做动画、圆角和透明度,这是它相对 SurfaceView 的优势。相机预览、视频播放需要和其它 UI 叠加时,常常选 TextureView。代价是它走 GPU 合成,功耗和延迟通常高于 SurfaceView 的 overlay。使用时要等 isAvailable 再绑定 SurfaceTexture,并在 onSurfaceTextureDestroyed 归还资源;频繁 resize 会触发纹理重建,容易闪一下。Android 7 之后若只需简单叠加,也可以看 SurfaceView 的 setChildSurfacePackage。
选型原则:要参与 View 动画就用 TextureView,要省电、低延迟就优先 SurfaceView。
三个容易踩的坑
- 要省电低延迟却选了 TextureView:它走 GPU 合成,功耗和延迟通常高于 SurfaceView 的 overlay。
- SurfaceView 盖不住控件:默认最底层,忘了
setZOrderMediaOverlay/setZOrderOnTop就被埋了。 - TextureView 频繁 resize 闪一下:纹理重建导致,绑定/归还资源(
isAvailable/onSurfaceTextureDestroyed)要配对。
什么时候用:游戏、相机、视频解码、投屏这类高帧率或需叠加 UI 的场景二选一;纯普通控件不需要碰 Surface 家族。
四、View 的滑动实现:六种方式及其适用边界
View 的滑动有六种常见方式,差异在于「动的是 View 还是内容」:
- a.
layout(left, top, right, bottom):修改 View 四个方向的属性值来改坐标,从而滑动 View 本身。 - b.
offsetLeftAndRight()/offsetTopAndBottom():指定偏移量滑动 View,比重新layout更轻。 - c. 改
LayoutParams:布局参数里保存了 View 的布局信息,通过修改布局参数(如 margin)来滑动 View。 - d. 动画移动:补间平移动画不能改变 View 的位置参数,属性动画才可以真正改位置。
- e.
scrollTo/scrollBy:移动的是 View 的内容。scrollBy(50, 50)你会看到屏幕上的内容向屏幕左上角移动——参考对象不同导致,可看作移动的是手机屏幕,屏幕向右下角移动,内容就像左上角移动了。 - f.
Scroller:需配合computeScroll实现滑动,它本身不会滑动 View,作用像插值器——计算当前时间点该滑到的距离,View 不断重绘、不断调用computeScroll(空方法需重写),从中获取当前位置并调scrollTo实现滑动。
三个容易踩的坑
- 以为补间平移动画改了位置:补间动画只改绘制位置不改布局参数,结束会「瞬移」回原处——要真改位置用属性动画。
- 混淆 scrollTo 的参考系:它移的是内容不是 View,方向直觉相反,容易滑反。
- Scroller 不重写 computeScroll 就没效果:Scroller 只是插值器,滑动动作必须自己写在
computeScroll里。
什么时候用:跟手拖动用 scrollTo/scrollBy + Scroller;动画改位置用属性动画;简单位移改 LayoutParams 或 offset* 即可。
附:速查表
| 场景 | 用什么 | 不要用什么 |
|---|---|---|
| 改颜色 / 局部重绘 | invalidate |
别用 requestLayout(会重走 measure/layout) |
| 改尺寸 / 增删子 View | requestLayout |
别只 invalidate(不重新排版) |
| 高帧率 / 省电低延迟(游戏、相机、视频) | SurfaceView(overlay 合成) |
别用 TextureView(GPU 合成更耗) |
| 需要动画、圆角、透明度叠加 | TextureView |
别用 SurfaceView(不在 View 树,动画别扭) |
| 真正改变 View 位置 | 属性动画 / 改 LayoutParams |
别用补间平移动画(不改位置参数) |
| 跟手滚动 | Scroller + computeScroll + scrollTo |
别在 draw 里分配对象(GC 抖动掉帧) |
| 卡顿排查 | Systrace / GPU 呈现模式 / Perfetto |
别只靠「换旗舰机」(先拆主线程长任务) |
| 子线程改 UI | 切回主线程再改 | 别在子线程直接改(ViewRootImpl 查线程会崩) |
Android 图形渲染与 View 体系:绘制、刷新与 Surface 家族
https://lautung.com/archives/android-graphics-and-view
评论