为什么会有UI优化的概念?因为UI过程过于复杂,就会导致界面、动画卡顿,影响用户体验。
屏幕上的图形、文字等,都是经过CUP和GPU的计算,然后绘制到屏幕上的,所以首要任务就是了解CPU和GPU的工作流程。
CPU的任务繁重,除做逻辑运算外,还要做内存管理、显示操作,因此在实际运算的时候性能会大打折扣,在没有GPU的时代,不能显示负责的图形,其运算速度远跟不上今天复杂三维游戏的要求。这时就需要专门负责显示计算的GPU。
黄色的 Control 为控制器,用于协调控制整个 CPU 的运行,包括取出指令、控制其他模块的运行等; 绿色的 ALU ( Arithmetic Logic Unit )是算术逻辑单元,用于进行数学、逻辑运算;
橙色的 Cache 和 DRAM 分别为缓存和 RAM ,用于存储信息。
从结构图可以看出, CPU 的控制器较为复杂,而 ALU 数量较少。因此 CPU 擅长各种复杂 的逻辑运算,但不擅长数学尤其是浮点运算。
CPU负责将图形元素进行一系列初步的运算,得到图形的纹理信息,然后将纹理信息丢给GPU,GPU负责将纹理信息进行更复杂的运算,得到最终的位图,显示器直接按照位图的像素信息渲染,就完成了显示图形的整个流程。
60Hz 刷新频率由来
12 fps :由于人类眼睛的特殊生理结构,如果所看画面之帧率高于每秒约 10-12 帧的时候,就会 认为是连贯的
24 fps :有声电影的拍摄及播放帧率均为每秒 24 帧,对一般人而言已算可接受
30 fps :早期的高动态电子游戏,帧率少于每秒 30 帧的话就会显得不连贯,这是因为没有动态模 糊使流畅度降低
60 fps 在与手机交互过程中,如触摸和反馈 60 帧以下人是能感觉出来的。 60 帧以上不能察觉 变化
当帧率低于 60 fps 时感觉的画面的卡顿和迟滞现象
Android 系统每隔 16ms 发出 VSYNC 信号 (1000ms/60=16.66ms) ,触发对 UI 进行渲染, 如果每次渲染都成 功这样就能够达到流畅的画面所需要的 60fps ,为了能够实现 60fps ,这意味着计算渲染的大多数操作都必须 在 16ms 内完成。
16 毫秒的时间主要被两件事情所占用
第一件:将 UI 对象转换为一系列多边形和纹理。
第二件: CPU 传递处理数据到 GPU 。
所以很明显,我们要缩短 这两部分的时间,也就是说需要尽量减少对象转换的次数,以及GPU上传数据的次数。
如何减少这两部分的时间 以至于在 16ms 完成呢?
CPU 减少 xml 转换成对象的时间
GPU 减少重复绘制的时间
Android主题会有背景,我们可以考虑去除这个背景,减少一次不必要的渲染。
另外,我们的布局文件如果不是业务需要,尽量不指定背景。
自定义view,减少过度绘制
先看效果
很明显,中间区域在未优化之前,三中图片有重叠部分,明显存在过度绘制问题。
优化前的代码:
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
mDroidCards.forEachIndexed { index, droidCard ->
if (index == mDroidCards.lastIndex) {
drawDroidCard(canvas, droidCard)
} else {
drawDroidCard(canvas, droidCard, index)
}
}
invalidate()
}
private fun drawDroidCard(canvas: Canvas?, droidCard: DroidCard, index: Int) {
// canvas?.save()
// canvas?.clipRect(droidCard.x.toFloat(), 0f, mDroidCards[index + 1].x.toFloat(), droidCard.height.toFloat())
drawDroidCard(canvas, droidCard)
// canvas?.restore()
}
private fun drawDroidCard(canvas: Canvas?, droidCard: DroidCard) {
canvas?.drawBitmap(droidCard.bitmap, droidCard.x.toFloat(), 0f, mPaint)
}
优化后的代码:
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
mDroidCards.forEachIndexed { index, droidCard ->
if (index == mDroidCards.lastIndex) {
drawDroidCard(canvas, droidCard)
} else {
drawDroidCard(canvas, droidCard, index)
}
}
invalidate()
}
private fun drawDroidCard(canvas: Canvas?, droidCard: DroidCard, index: Int) {
canvas?.save()
canvas?.clipRect(droidCard.x.toFloat(), 0f, mDroidCards[index + 1].x.toFloat(), droidCard.height.toFloat())
drawDroidCard(canvas, droidCard)
canvas?.restore()
}
private fun drawDroidCard(canvas: Canvas?, droidCard: DroidCard) {
canvas?.drawBitmap(droidCard.bitmap, droidCard.x.toFloat(), 0f, mPaint)
}
结论:
通过canvas.clipRect方法,裁剪出需要绘制的区域,避免重复绘制。
使用Layout Inspector检查View hierarchy。
这里可以清晰看到View的树桩结构,我们需要减少树的层级,减少控件的渲染时间。
可以使用merge标签简化层级。