SurfaceView的使用以及缓冲机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/chendeshan330/article/details/76686791

 

背景

基于有些动画要求帧数比较高,具有实时性,传统的View刷新的帧数不高,并且会出现卡顿的现象,而SurfaceView就能解决这个问题。

概念

Provides a dedicated drawing surface embedded inside of a view hierarchy.You can control the format of this surface and, if you like, its size; theSurfaceView takes care of placing the surface at the correct location on thescreen

SurfaceView是View的子类,这个视图里内嵌了一个专用于绘制的SurfaceView,你可以控制这个Surface的尺寸和格式。Surfaceview控制这个Surface的绘制位置。

 

The surface is Z ordered so that it is behind the window holding itsSurfaceView; the SurfaceView punches a hole in its window to allow its surfaceto be displayed. The view hierarchy will take care of correctly compositingwith the Surface any siblings of the SurfaceView that would normally appear ontop of it. This can be used to place overlays such as buttons on top of theSurface, though note however that it can have an impact on performance since afull alpha-blended composite will be performed each time the Surface changes. Thetransparent region that makes the surface visible is based on the layoutpositions in the view hierarchy. If the post-layout transform properties areused to draw a sibling view on top of the SurfaceView, the view may not beproperly composited with the surface.

 

surface是纵深排序(Z-ordered)的,这表明它总在自己所在窗口的后面。surfaceview提供了一个可见区域,只有在这个可见区域内的surface部分内容才可见,可见区域外的部分不可见。surface的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。这意味者surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)。注意,如果surface上面有透明控件,那么它的每次变化都会引起框架重新计算它和顶层控件的透明效果,这会影响性能。

 

Access to the underlying surface is provided via the SurfaceHolderinterface, which can be retrieved by calling getHolder().

你可以通过SurfaceHolder接口访问这个surface,getHolder()方法可以得到这个接口。

 

The Surface will be created for you while the SurfaceView's window isvisible; you should implement surfaceCreated(SurfaceHolder) andsurfaceDestroyed(SurfaceHolder) to discover when theSurface is created and destroyed as the window is shown and hidden.

surfaceview变得可见时,surface被创建;surfaceview隐藏前,surface被销毁。这样能节省资源。如果你要查看surface被创建和销毁的时机,可以重载surfaceCreated(SurfaceHolder)和surfaceDestroyed(SurfaceHolder)。

 

One of the purposes of this class is to provide a surface in which asecondary thread can render into the screen. If you are going to use it thisway, you need to be aware of some threading semantics:

surfaceview的核心在于提供了两个线程:UI线程和渲染线程。这里应注意

 

·        All SurfaceView and SurfaceHolder.Callback methods will be calledfrom the thread running the SurfaceView's window (typically the main thread ofthe application). They thus need to correctly synchronize with any state thatis also touched by the drawing thread.

所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用,一般来说就是应用程序主线程。渲染线程所要访问的各种变量应该作同步处理。

 

·        You must ensure that the drawing thread only touches the underlyingSurface while it is valid -- between SurfaceHolder.Callback.surfaceCreated()and SurfaceHolder.Callback.surfaceDestroyed().

由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和

 SurfaceHolder.Callback.surfaceDestroyed()之间有效,所以要确保渲染线程访问的是合法有效的

注意:

SurfaceView就是在Window挖了一个洞,他就是显示在这个洞里,其他的View是显示在Window上。

对于View执行绘制操作只能在UI线程上,绘制完成后需要调用View.Invalidate方法通知系统刷新View。而Surface是可以允许非UI线程绘制图像。

SurfaceView的生命周期

1,  打开Activity

surfaceCreated()->surfaceChanged()

2,点击back:

surfaceDestroyed()

3,点击Home键

surfaceDestroyed()

4,回到前台

surfaceChanged()->surfaceCreated()

5,屏幕锁定

不调用任何方法。

SurfaceView的使用

SurfaceView并不同于常规的View,对SurfaceView的控制是通过SurfaceHolder操作的。详细请看代码吧。

//继承SurfaceView并且实现CallBack方法

public class MySurfaceview extends SurfaceView implementsSurfaceHolder.Callback {
    private int distance;
    private int i;
    private int j;

    private final SurfaceHolder holder;

    public MySurfaceview(Context context){
        super(context);
        holder = getHolder();//初始化的时候得到SurfaceHolder
        holder.addCallback(this);//设置接口。
    }


;//当SurfaceView创建好的时候调用
    @Override
    public voidsurfaceCreated(SurfaceHolder holder) {
        //进行绘制操作  

    }


    //当SurfaceView大小和格式改变的时候调用。
    @Override
    public voidsurfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }
//当SurfaceView销毁的时候调用
    @Override
    public voidsurfaceDestroyed(SurfaceHolder holder) {
//进行一些资源回收,和标志位的更新等操作。
    }
    }

SurfaceView的绘制

      Surface在SurfaceHolder对象中,虽然Surface保存了当前窗口的像素数据,但是不能够直接操作Surface对象,而是通过SurfaceHolder的LockCavas()方法获得Canvas对象,通多对Canvas的操作来简介操作Surface。如果只修改Surface中部分内容,为了提高效率则只重回变化的部分可以调用lockCanvas(Rect dirty)方法,来绘制指定的区域,这样改区域外的内容会缓存起来。

       SurfaceView中的lockCanvas()和unlockCanvasAndPost(Canvascanvas)函数实现的同步锁机制,这样可以保证在Surface绘制过程中不会被改变。当绘制完成后调用unlockCanvasAndPost(Canvas canvas)函数,将内容显示出来。

       注意:lockCanvas()和unlockCanvasAndPost()方法是成对出现的,不能连续lockCanvas(),因为canvas已经被锁定,也不能连续unlockCanvasAndPost(),因为Canvas已经被提交,否则会抛出异常。

SurfaceView中的双缓冲机制

自己对双缓冲机制也不是十分明白,看了网上的说法不一,所以实践是检验真理的唯一标准,自己写了一下Demo进行了一下测试。

初始化SurfaceView之后,一次填充了白色,灰色,蓝色。

//填充白色

public void fillWhiteColor() {
        Canvas canvas =holder.lockCanvas();
        canvas.drawColor(Color.WHITE);
        holder.unlockCanvasAndPost(canvas);
    }


//填充灰色
   

 public void fillGrayColor() {
        Canvas canvas =holder.lockCanvas();
        canvas.drawColor(Color.GRAY);
       holder.unlockCanvasAndPost(canvas);
    }


//填充蓝色
  

  public void fillBlueColor() {
        Canvas canvas =holder.lockCanvas();
        canvas.drawColor(Color.BLUE);
        holder.unlockCanvasAndPost(canvas);
    }


而之后多次就绘制数字到屏幕上。会出现一下现象。

白色背景出现的数字为:3,6,9,12...

灰色背景出现的数字为:1,4,7,10…

蓝色背景上出现的数字为:2,5,8,11…

 

 

测试手机为nexus5。

结论:这是surfaceView的缓冲机制造成的,而在Android4.1之后会引入三缓冲机制,使用lockCanvas(),方法获取到的是三个画布轮流切换,在Android4.1之前默认是两个,而之后默认是三个,厂商可以根据需求自己定制,但是最多可以使用32个缓冲区。

 //关键代码如下:

public class MySurfaceview extends SurfaceView implementsSurfaceHolder.Callback {
    private int distance;
    private int i;
    private int j;

    private final SurfaceHolder holder;

    public MySurfaceview(Context context){
        super(context);
        holder = getHolder();
        holder.addCallback(this);
    }

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

    }

    @Override
    public voidsurfaceDestroyed(SurfaceHolder holder) {

    }

    public void draw() {
        Paint paint = new Paint();
        paint.setTextSize(30);

        paint.setColor(Color.RED);

        Canvas canvas =holder.lockCanvas();

       canvas.drawText(String.valueOf(i), getWidth() / 2, distance, paint);
        i++;
        distance = distance + 40;
       holder.unlockCanvasAndPost(canvas);
    }


    public void fillWhiteColor() {
        Canvas canvas =holder.lockCanvas();
        canvas.drawColor(Color.WHITE);
        holder.unlockCanvasAndPost(canvas);
    }

    public void fillGrayColor() {
        Canvas canvas =holder.lockCanvas();
        canvas.drawColor(Color.GRAY);
       holder.unlockCanvasAndPost(canvas);
    }

    public void fillBlueColor() {
        Canvas canvas =holder.lockCanvas();
        canvas.drawColor(Color.BLUE);
        holder.unlockCanvasAndPost(canvas);
    }

    public void lockRect() {
        Paint paint = new Paint();
        paint.setTextSize(30);
        j++;
        Canvas canvas =holder.lockCanvas(new Rect(getWidth() / 2 - 60, 0, getWidth() / 2 - 30,getHeight())

);

       canvas.drawText(String.valueOf(j), getWidth() / 2 - 45, getHeight() / 2,paint);

        holder.unlockCanvasAndPost(canvas);
    }
}

解决方案

 

一般游戏里说的双缓冲防止画面闪烁,只是每一帧先绘制到bitmap再绘制到SurfaceView的canvas。

LockCanvas(Rect dirty)

由于surfaceview是多缓冲机制,所以lockCanvas(Rect)是修改rect内的内容。而调用lockCanvas(rect)方法会把当前页的内容填充到下一个缓冲区。有demo自己可以测试。

 

遗留问题

进入到surfaceview页面,画的第一个背景不能填充到缓冲区,不知道为什么,知道的同学们可以留个言,谢谢大家。

阅读更多
换一批

没有更多推荐了,返回首页