这篇文章的内容, 原来是写在随笔10 里面的, 写着写着内容多了, 还是放到新的文章里面吧, 后面再添加其他的也方便.
前面我们已经知道了, View 的更新都是在 UI Main 线程中进行的, 有没有例外的呢? 答案是确定的!!!!
- SurfaceView, 这是一个可以"异步" 并且"局部" 更新的 View, 适用于动作游戏和频繁更新的页面.
- 异步, 更新 SurFaceView 的显示内容, 我们可以开多一个线程来进行, 这个线程通过 SurfaceView对象的变量 SurfaceHolder对象, 获取一个Canvas 对象, 在该Canvas 对象上画图, 最后把 Canvas 的内容更新到屏幕上(标准的双缓冲). 这个过程是不必在UI Main 线程中进行的.
- 局部, 上面的Canvas(画布) 对象, 默认是获取整个SurfaceView 的视图大小的画布, 但是有时候, 我们可能更新的地方只有某个角落. 我们可以在获取 Canvas 对象的时候, 通过 Rect 对象参数来指定一个矩形区域, 在本次的更新中, 将只更新这个区域, 其他区域将不会被更新到, 从而提高效率.
- 在头两个画面中, 由于Canvas对象都为空, 所以这两个画面不存在局部更新, 都是全屏更新 . 为什么头两个画面的 Canvas对象为空, 请看下面的内容.
在官方文档中, "Dev Guide" -> 左边菜单点击 "Graphics", 在出来的文档中, 最后有一段话值得注意:
Draw with a Canvas
On a SurfaceView
Note: On each pass you retrieve the Canvas from the SurfaceHolder, the previous state of the Canvas will be retained. In order to properly animate your graphics, you must re-paint the entire surface. For example, you can clear the previous state of the Canvas by filling in a color with
or setting a background image with drawColor()
. Otherwise, you will see traces of the drawings you previously performed.drawBitmap()
1 2 3 4 => 更新的帧数
A B => 1,2帧 更新Canvas, 后面的帧不再画东西
A B => 保留的前一个Canvas状态
A B A B => 显示内容, 只考虑更新区域(局部或全屏).
- 在第一帧的时候, 获取的 canvas为空, 画上东西A, 画布状态 canvasA, 其前一个状态为空.
- 到第二帧的时候, 获取的 canvas 为canvasA 的前一个状态(- -, 让人看不懂)为空, 画上东西B, 画布状态 canvasB, 其前一个状态为 canvasA.
- 到第三帧的时候, 获取的 canvas 为canvasB 的前一个状态 canvasA, 没画东西, 画布状态 canvasA, 其前一个状态为 canvasB.
如果有画东西C, 如果画的地方没有drawColor() 或 drawBitmap(), 那么画布状态为 canvasAC, 注意是重叠 , 而非覆盖, 所以在进行动画的区域, 请先清屏了再画, 不然将有前两帧的画面痕迹.
drawColor() 是全屏清屏, drawBitmap() 可以自己控制范围, 该方法的重载方法很多.
- 到第四帧的时候, 获取的 canvas 为canvasA 的前一个状态 canvasB, 没画东西, 画布状态 canvasB, 其前一个状态为 canvasA.
如果第三帧有画东西C, 并且这帧也画了东西D, 那画布状态为 canvasBD, 其前一个状态为 canvasAC.
- 照此循环下去, 结果就是两个画面不停的交替, 本人测试的结果确实是这样.
在N-2>0的情况下, 第N 帧获取的画布竟然是 N-2 帧的画布, 而不是延续上一帧的画布, 并且画布被锁定了, 想换都不行.
本人菜鸟, 不是很理解android 这样做的用意. 让画面交替显示, 明显大部分情况下都不需要这样做吧, 就算需要交替画面, 我们也可以自己控制的. 并且第二帧过后, 我们每个SurfaceView 都将拥有2 个canvas 资源, 难道canvas 资源不值钱!!!!!
难道是 N-1 帧的canvas 在"页面显示后" 至 "新页面显示前" 这个过程中被系统或其他资源 "绑架" 了, 不给其他人动用, 本人无限 YY 中!!!!!
- 如果动画只在局部更新, 为了让局部更新区域外的区域能显示同一个画面, 我们需要在第一和第二帧连续画同样的东西.
画 AB AC D E F G =>A画在不更新的区域, BCD...G 画在更新区域
显示 AB AC AD AE AF AG
- 上面的情况, 如果第一和第二帧还没画 A ,但是在后面要显示A, 怎么办呢? 其实很简单, 在需要显示A 的时候, 把动画变为全屏更新, 连续画 A 两次后, 再把动画切换为局部更新模式.
局部更新 ->全屏更新->局部更新
画 B C D E AF AG H I J
显示 B C D E AF AG AH AI AJ =>设定A占有的区域比其他的多
- 如果动画是全屏更新, 那只要我们连续两帧画同样的东西, 那画面就能一直显示同一个画面.
画 A B C C D E F
显示 A B C C CD CE CF =>设定C占有的区域比其他的多.
ps. 获取全屏更新的画布---------->canvas = Holder.lockCanvas();
获取局部更新的画布----------->canvas = Holder.lockCanvas(new Rect(30,30,160,160)); //范围Rect对象自己定义.
Holder 为SurfaceView 的SurfaceHolder 对象变量.
双缓冲技术, 双即二! 一个缓冲是什么? 另一个缓冲又是什么? 本人一直的理解是, 一个缓冲是内存(甚至是CPU的缓存), 一个是显示设备.
画图的耗时和显示的耗时相比, 前者很多情况下明显要大于后者, 如果画图的操作过于复杂, 那这个差距就更大了. 一边画, 一边更新到屏幕上, 由于更新的频率快并且不均匀, 将造成一个后果--闪烁.
对于闪烁问题, 我们的前辈很聪明, 给出了一个双缓冲的方法. 很多情况下是, 我们先把显示内容画在内存中的一张位图上, 画好了, 再把这张位图拷贝到屏幕上. 这样做的好处是, 位图拷贝到屏幕上的耗时是比较稳定的, 在合理频率下, 画面的显示就会很稳定. 至于画图的操作有多复杂多困难, 跟画面显示没直接的关系了, 画面显示只能内存打交道, 画图也是在内存中暗地里进行着.
以上是个人的理解, 没有参考什么权威说法之类的东东, 反正我不认同那些说 SurfaceView 因为用了两个canvas, 所以是双缓冲的说法.
现在比较新的UI 框架, 有哪个的内置控件(组件)不是双缓冲的呢? 一般人能想到的, 那些大公司的高手没可能想不到, 控件都闪烁, 还不给人笑死.