Android高级UI_双缓冲策略解决GPU卡顿

在上一篇中有讲到绘制一个水波纹效果,当打开此界面过久时,会有明显的卡顿,查看内存很稳定,

内存

这时候我们先打开开发者选项里的”GPU呈现模式分析“,设置为“在屏幕上显示为条形图”(不同的手机可能有略微的差异,我这里用的是小米)。


优化前GPU绘制

可以看到,当重复绘制时,GPU的负荷太高,卡顿也就再所难免。

如何解决

当我们只绘制一段水波纹不使用canvas.clipPath(mPath, Region.Op.INTERSECT);时,查看

单独绘制

此时的GPU显示比较稳定的,那么现在有一个问题,画水波纹和画圆是两个独立的动作,能不能分开执行,答案是必须要等onDraw方法执行完成之后,才会把数据交给GPU去处理展示。这就是android绘图当中的第一道缓冲,即显示缓冲区。
而所谓的双缓冲,在android绘图中其实就是再创建一个Canvas和对应的Bitmap,然后在onDraw方法里默认的Canvas通过drawBitmap画刚才new的那个bitmap从而实现双缓冲。用代码简单的表述是这样的:

private void init(){
    Bitmap bufferBm = Bitmap.create(getWidth,getHeight,Bitmap.Config.ARGB_8888);
    Canvas bufferCanvas = new Canvas(bufferBm);
}

private void drawSomething(){
    bufferCanvas.drawXxx();
    bufferCanvas.drawXxx();
    ...
    invalidate();
}

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

双缓冲绘图的优缺点及适用场景

现在我们来改进上一篇的代码

...
        mPaintClear = new Paint();
        mPaintClear.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
...
...
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                offset = (int) animation.getAnimatedValue();
                if (mScreenWidth > 0 && mScreenHeight > 0) {
                    drawWave(offset);
                    postInvalidate();
                }
            }
        });
...
  private void drawWave(int offset) {
        if (mBufferBitmap == null) {
            mBufferBitmap = Bitmap.createBitmap(mScreenWidth, mScreenHeight, Bitmap.Config.ARGB_8888);
            mBufferCanvas = new Canvas(mBufferBitmap);
        }
        //画圆
        mBufferCanvas.drawCircle(mScreenWidth / 2, mCenterY, 300, mPaint2);

        mPath.reset();
        mPath.moveTo(-mWL + offset, mCenterY);
        for (int i = 0; i < mWaveCount; i++) {
            mPath.quadTo((-mWL * 3 / 4) + (i * mWL) + offset, mCenterY + 60, (-mWL / 2) + (i * mWL) + offset, mCenterY);
            mPath.quadTo((-mWL / 4) + (i * mWL) + offset, mCenterY - 60, i * mWL + offset, mCenterY);
        }
        mPath.lineTo(mScreenWidth, mScreenHeight);
        mPath.lineTo(0, mScreenHeight);
        mPath.close();

        mPath2.addCircle(mScreenWidth / 2, mCenterY, 300, Path.Direction.CCW);
        //api19以上才能用
//        mPath.op(mPath2, Path.Op.INTERSECT);
        //改用canvas.clipPath
        mBufferCanvas.clipPath(mPath2, Region.Op.INTERSECT);
        mBufferCanvas.drawPath(mPath, mPaint);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /*
            使用双缓存策略,减轻卡顿
         */
        if (mBufferBitmap == null) {
            return;
        }
        canvas.setDrawFilter(pfd);
        canvas.drawBitmap(mBufferBitmap, 0, 0, null);
    }

GPU绘制是正常了,我的波纹呢???
大写的黑人问号,其实问题出在上面,

if (mBufferBitmap == null) {
            mBufferBitmap = Bitmap.createBitmap(mScreenWidth, mScreenHeight, Bitmap.Config.ARGB_8888);
            mBufferCanvas = new Canvas(mBufferBitmap);
        }

只要在绘制前把它清屏就好了

        mPaintClear = new Paint();
        mPaintClear.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        mBufferCanvas.drawColor(Color.TRANSPARENT);
        //清屏
        mBufferCanvas.drawPaint(mPaintClear);

我们再调试一把

优化后

从上面的实验数据我们可以得出结论:

  • 在绘制数据量较小时,不使用双缓冲,GPU的负荷更低,即绘制性能更高;
  • 在绘制数据量较大时,使用双缓冲绘图,绘制性能明显高于不使用双缓冲的情况;
  • 使用双缓冲会增加内存消耗。
    其实上面的结论也很好理解,就像上面举的搬砖的例子,如果砖少的话,用车来拉明显是划不来的,砖的数量很多的时候,用车来拉就可以节省很多时间,但是用车就要消耗额外的资源,这就需要根据不同的情况做出正确的选择。

android的双缓冲绘图技术今天就先讲到这里,有不对的地方或大家有什么问题欢迎留言。
github代码下载
也欢迎大家关注我的CSDNgithub主页

推荐阅读更多精彩内容