Android SurfaceView的双缓冲机制,引起的闪屏问题


SurfaceView相关目录


SurfaceView要点

  • SurfaceView拥有独立的Surface(绘图表面)
  • SurfaceView是用Zorder排序的,他默认在宿主Window的后面,SurfaceView通过在Window上面“挖洞”(设置透明区域)进行显示

SurfaceView与View的区别

  • View的绘图效率不高,主要用于动画变化较少的程序
  • SurfaceView 绘图效率较高,用于界面更新频繁的程序
  • SurfaceView拥有独立的Surface(绘图表面),即它不与其宿主窗口共享同一个Surface。
    一般来说,每一个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描述它的绘图表面。对于那些具有SurfaceView的窗口来说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer或者LayerBuffer,用来单独描述它的绘图表面,以区别于它的宿主窗口的绘图表面。因此SurfaceView的UI就可以在一个独立的线程中进行绘制,可以不会占用主线程资源。
  • SurfaceView使用双缓冲机制,播放视频时画面更流畅

什么是双缓冲机制

  • 在运用时可以理解为:
    SurfaceView在更新视图时用到了两张 Canvas,一张 frontCanvas 和一张 backCanvas ,每次实际显示的是 frontCanvas ,backCanvas 存储的是上一次更改前的视图。当你在播放这一帧的时候,它已经提前帮你加载好后面一帧了,所以播放起视频很流畅。
  • 当使用lockCanvas()获取画布时,得到的实际上是backCanvas 而不是正在显示的 frontCanvas ,之后你在获取到的 backCanvas 上绘制新视图,再 unlockCanvasAndPost(canvas)此视图,那么上传的这张 canvas 将替换原来的 frontCanvas 作为新的frontCanvas ,原来的 frontCanvas 将切换到后台作为 backCanvas 。例如,如果你已经先后两次绘制了视图A和B,那么你再调用 lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你将重绘的 A 视图上传,那么 A 将取代 B 作为新的 frontCanvas 显示在SurfaceView 上,原来的B则转换为backCanvas。
    相当于多个线程,交替解析和渲染每一帧视频数据。

双缓冲机制导致的闪屏问题

  • 原因
    第一次绘制的时候是操作的缓存A,这次绘制是有绘制背景颜色,但是下次绘制缓存B的时候不绘制背景,这种情况下他们交替显示的时候就会出现背景快速变换而导致的闪屏现象。
  • 解决办法
    核心是创建一个Bitmap bitmapCache;
    每次画图时,都在bitmapCache继续画画,最后再投到屏幕上去。
    这样做的原理是,每次绘制都在上一次的基础上绘制,使帧连贯
			    if (bitmapCache == null) {
                    bitmapCache = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
                }
                Canvas canvasCache = new Canvas(bitmapCache);
                drawCache(canvasCache);
                canvas.drawBitmap(bitmapCache, 0, 0, null);
  • 完整代码
package com.example.zhangyu.cutvideomusic;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.zhangyu.cutvideomusic.utils.ThumbUtils;

public class BgSurfaceView extends SurfaceView {

    private SurfaceHolder holder;
    private Canvas canvas;
    private Thread thread;//用于绘制的线程
    private boolean isRunning;//线程的控制开关

    public BgSurfaceView(Context context) {
        this(context, null);
    }

    public BgSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        holder = getHolder();
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                isRunning = true;
                thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //不断绘制
                        while (isRunning) {
                            draw();
                        }
                    }
                });
                thread.start();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                isRunning = false;
            }
        });
    }

    Bitmap bitmapCache;

    private void draw() {
        //SurfaceView被销毁,但是子线程可能还在进行操作,可能抛出一些异常
        try {
            canvas = holder.lockCanvas();
            //按home或者back,SurfaceView被销毁,所以需要判空canvas
            if (canvas != null) {
                if (bitmapCache == null) {
                    bitmapCache = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
                }
                Canvas canvasCache = new Canvas(bitmapCache);
                drawCache(canvasCache);
                canvas.drawBitmap(bitmapCache, 0, 0, null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (canvas != null) {
                                try {
                                    //手动try catch一下这个方法,让程序在4.3的手机上不至于崩溃,部分Android13也会崩溃
                                    holder.unlockCanvasAndPost(canvas);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
            }
        }
    }

    private void drawCache(Canvas canvas) {
        //todo 开始绘制
        
     }

}

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值