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 ES
API进行绘制,可以更加针对性的高性能绘图。 - 如果资源图片比较大,考虑放在
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 布局优化
- 性能优化之布局优化