Android Performance: clipRect和quickReject

  Android Framework会通过裁剪(Clipping)的方式避免重绘不可见的元素,以此来优化性能。但这一优化对于一些复杂的自定义View无效,如果自定义View重写了onDraw(),系统无法知道View中各个元素的位置和层级关系,也就无法自动省略绘制不可见的元素。Canvas提供了一些特殊的方法,可以用来向Android Framework告知Canvas的哪些部分不可见、不需要绘制。其中最常用的方法是Canvas.clipRect(),可以定义绘制的边界,边界以外的部分不会进行绘制。Canvas.quickReject()可以用来测试指定区域是否在裁剪范围之外,如果要绘制的元素位于裁剪范围之外,就可以直接跳过绘制步骤。

  下面的示例代码可以在这里找到。在开发者模式中打开Debug GPU overdraw,运行结果如图1所示,三个自定义View叠放在一起,重叠部分出现Overdraw。

图1

图1

  在DroidCardsView.java中可以看到自定义View的绘制方法:

这里只是按顺序依次绘制各个卡片,上面的卡片会盖住下面的卡片,卡片间重叠的部分也会进行绘制,发生Overdraw。由于重写了onDraw(),Android Framework无法自动对绘制进行优化,需要手动告诉Android Framework那些部分是不需要绘制的。

  下面使用CanvasclipRect(float left, float top, float right, float bottom)手动指定被覆盖的卡片区域:

首先计算卡片左边的位置mCardLeft;然后调用canvas.save()保存canvas的当前状态;接着通过canvas.clipRect()指定要绘制的区域,mCardLeft + mCardSpacing就是每张卡片露出来的宽度;使用drawDroidCard()进行绘制后,调用canvas.restore()撤销之前canvas.clipRect()的配置。注意顶部的卡片是完全可见的,需要完整地绘制出来。

  修改后的效果如图2所示,可见重叠部分不会被绘制了。

图2

图2

  注意上面的例子在canvas.clipRect()前通过canvas.save()保存当前状态,并在canvas.clipRect()后通过canvas.restore()恢复到之前的状态。如果不使用canvas.restore()恢复canvas的状态,canvas.clipRect()的修改会一直生效,影响之后的绘制。