双缓冲绘图与SurfaceView(一):双缓冲绘图

前言

为了方便后面描述,首先需要明确:每个canvas都持有一个bitmap,在canvas上进行的绘制操作(即调用canvas.drawXXX(...)),实际上都是在bitmap上进行绘制。

第一层缓冲

cpu访问内存的速度要远远快于访问屏幕的速度。如果我们需要在屏幕上绘制100个图形,有两种方案:第一种方案是每次从内存中读取一个图形然后将此图形绘制到屏幕上,这样的过程重复进行100次——因为需要访问100次内存和100次屏幕,可想而知这种方案是非常耗时的;第二种方案是每次从内存中读取一个图形并绘制到内存中的一个临时bitmap上,重复进行100次,然后再一次性将内存中绘制好的临时bitmap绘制到屏幕上——显然,这种方案可以节约大量的时间,因为只需要访问一次屏幕。这第二种方案就是双缓冲绘图中的第一层缓冲。

android系统View类的onDraw方法中已经实现了这第一层缓冲,这从我们以往的开发经验中就很容易发现:在对onDraw提供的canvas进行绘制时,并不是绘制一点就显示一点,而是onDraw方法中的绘制工作全部完成后,才一次性将绘制的全部内容显示到屏幕上。这里的canvas中的bitmap就相当于前面所说的“内存中的临时bitmap”。

第二层缓冲

要解决的问题

通常情况下,在自定义View中,所有的绘图工作都是在UI线程中进行的(onDraw(…)方法运行于UI线程)。如果要绘制的图像非常复杂,耗时较多,并且需要频繁绘制的话,那将会造成UI线程的长时间阻塞。结果是,系统无法及时对用户的点击、触摸等事件做出响应(系统处理点击、触摸等事件都是在UI线程中进行的),会严重影响用户体验。

第二层缓冲

以绘制一个含有10000个图形(如圆形、矩形、线条等)的复杂图像为例,第二层缓冲指的是:并不直接在onDraw方法的canvas中绘制这10000个图形,而是**(1)先将这10000个图形绘制到一个临时的canvas中,绘制完成之后,(2)**再将此临时canvas中的内容(也就是一个bitmap),通过canvas.drawBitmap绘制到onDraw方法的canvas中。

第二层缓冲的主要作用是可以减少绘图工作对UI线程的阻塞,其利用的原理就是上面的操作(2)要快于操作(1)(因为操作(2)相当于就是简单的bitmap拷贝)。

具体做法

我们可以创建一个临时canvas,称为tempCanvas,如下图所示:

先在一个子线程中将10000个图形绘制到tempBitmap中,然后调用postInvalidate触发onDraw方法,在onDraw方法中(UI线程),将tempBitmap绘制到onDraw方法中的canvas中。因为在UI线程进行的工作只是简单的bitmap拷贝,因此此举能大大减少绘图工作对UI线程的阻塞。这其实也是SurfaceView减少UI线程阻塞的原理。

//创建临时的canvas并绘制
tempBitmap = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.RGB_565);
Canvas tempCavas = new Canvas(cacheBitmap);
tempCavas.drawXXX(...);
tempCavas.drawXXX(...);
...

@Override
protected void onDraw(Canvas canvas) {
	...
    canvas.drawBitmap(tempBitmap, 0, 0, null);
	...
}

第二层缓冲能减少绘图工作对UI线程的阻塞,但不一定能提升通过频繁绘制来实现的动画的流畅度。分为两种情形:

1.如果每一帧需要绘制的图像都不同,则绘制每一帧都需要执行操作(1)和操作(2),那么动画将更为卡顿。因为:首先,绘制一帧图像所需的时间 = 子线程中的绘制时间 + UI线程中的绘制时间,比不使用第二层缓冲时耗时更多;第二,在子线程和UI线程中都要用到tempBitmap,一个写入,一个读取,写入和读取不能并行,这就意味着,当在子线程中绘制完第n帧,并将tempBitmap交给UI线程之后,子线程并不能立即开始绘制第下一帧,因为tempBitmap此时被UI线程占用了,必须等到UI线程中的绘制工作完成之后,子线程才能在tempBitmap上继续绘制下一帧——也就是说,每一帧的绘制工作相当于是排队进行的,无法并行。综合以上两点,总的结果就是,单位时间内可以绘制的帧数比不使用双缓冲时更少了。

2.如果动画每一帧需要绘制图像都一样(即只是简单的整体位移、缩放等效果),或者动画每一帧需要绘制的图像只是有局部的细微差别,那么通过对tempBitmap的复用,或者只是对tempBitmap进行局部重绘,就可以大大缩短操作(1)的耗时。在这种情况下,使用第二层缓冲会使得动画更为流畅。

发布了46 篇原创文章 · 获赞 38 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/al4fun/article/details/72796334
今日推荐