Android绘制
I. 布局简单优化
- 尽量少的布局层级
LinearLayout性能比RelativeLayout稍高ViewStub代替include来引用不常用的布局- 用
merge来代替根节点是FrameLayout,并且不需要background或padding等属性时。 - 用
merge来代替include的顶节点,这样被引入时顶节点会自动被忽略。
II. 绘制相关深度优化
可以通过Hierarchy Viewer可视化布局,直观的看布局层级分布以及各View
measure、layout、draw的耗时。
可以通过traceview,计算出每个方法所占用的CPU时间。
- 已知View大小的,自定义View,
onMeasure时直接setMeasuredDimension - 已知布局或者其他特定规律的,直接自定义View,达到减少层级,针对性
measure、layout、draw - 如果布局含有复杂的动画,或者需要复杂的绘制,考虑在独立的绘制线程处理,而不block UI线程,此时考虑
SurfaceView或TextureView(Android 4.0引入)(相比SurfaceView而言,可以像常规视图一样被改变) - 使用
OpenGL ESAPI进行绘制,可以更加针对性的高性能绘图。 - 如果资源图片比较大,考虑放在
drawable-nodpi或者直接放在asset,防止获取资源的时候缩放暂用大量内存,也产生不必要的延时。
III. LayoutInflater
- 使用XmlPull来解析
rInflate()方法(中不断递归)遍历根布局下的子布局- 由于
setContentView默认是添加到id为content的FrameLayout中,因此LyoautParams有效。
最终结果:
是一个完整的DOM结构,返回的是顶层布局。
耗时点:
- 其中的
createView()方法中通过反射创建出View实例
IV. 绘制过程
开始
ViewRoot的performTraversals()
onMeasure()
从measure()中调用,每个View都有一次measure()的过程.
参数: 规格和大小: MeasureSpec = specSize | specMode
规格说明:
| 名称 | 注解 |
|---|---|
| EXACTLY | 希望子视图大小 由 specSize决定 |
| AT_MOST | 希望子视图大小 保证不超过 specSize |
| UNSPECIFIED | 希望子视图 任意大小(很少遇到) |
widthMeasureSpec、heightMeasureSpec参数由来:
- 一般情况: 由父布局计算得到
- 根布局: 由
getRootMeasureSpec()处理得到:
根布局给的参数 | 规格 | 大小
-|-MATCH_PARENT | EXACTLY | 视窗大小WRAP_CONTENT | AT_MOST | 视窗大小
给定大小 | EXACTLY | 给定大小
ViewGroup的measure:
遍历child View,进行
measureChild
结合ViewGroup的规格与大小,以及child规格与大小获得参数传入child View进行子布局的measure
结束:
将最终结果通过setMeasuredDimension设置最终测量的结果,一次measure过程结束
注意:
setMeasuredDimension()后getMeasuredWidth和getMeasuredHeight才是有效值。
onLayout()
紧接着measure之后,就是布局,确定位置。调用View的layout()方法触发。
决定是否需要onLayout
layout()中,首先会调用setFrame()方法来判断 视图是否发生过变化。- 或者
layout()中,有LAYOUT_REQUIRED(请求onLayout)
onDraw()
紧接着layout()之后,就是真正的绘制。调用View的draw()方法触发
步骤一,绘制背景
步骤二,为了淡入淡出做准备(一般没有)
如果有的情况下,一般情况下没有,就是保存canvas的Layers
步骤三,绘制内容
调用onDraw(Canvas),默认是空方法,这一部分是case by case
步骤四,绘制子View
调用dispatchDraw(Canvas),默认空方法,这一部分也是case by case
步骤五,绘制淡入淡出
如果有的情况下,绘制,然后还原canvas的Layer
步骤六,绘制滚动条
其实每个View都可以有滚动条的。
V. 视图状态
这里只提到需要特别注意到的。
View的视图状态变化,会回调
View#drawableStateChange()
focused
requestFocus()不能保证一定能获取到焦点,返回值为true才表示获取成功。需要focusable && focusable in touch mode- 一个界面只有一个焦点
window_focused
- 应用程序不能改变,由系统控制
- 表示视图是否处于正在交互的窗口中
selected
- 一个界面中可以有多个选中态
pressed
- 实际上应用程序也可以通过
setPressed()方法来控制的
VI. 状态变化回调

VII. View#invalidate
需要注意
invalidate虽然最终调到performTraversals()但是很可能没有 重新测量标志,大小没有变化,因此不会执行measure和layout,只有draw可以执行到。
相比之下如果希望视图绘制流程完整重新走一遍,需要调用requestLayout。

ps 第四篇是一些简单的应用层,就没有整理了
- Google I/O 2013 - Writing Custom Views for Android
- Android LayoutInflater原理分析,带你一步步深入了解View(一)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图状态及重绘流程分析,带你一步步深入了解View(三)
- Android 布局优化
- 性能优化之布局优化