首頁 > 軟體

Android系統view與SurfaceView的基本使用及區別分析

2022-03-31 16:00:45

一、引入:

Android提供了View來進行繪圖處理,在大部分情況下,View都能滿足繪圖需求。大家都知道View是通過重新整理來重繪檢視,Android系統通過發出VSYNC訊號來進行螢幕的重繪,重新整理的間隔時間為16ms。如果在16ms內View完成了你所需要執行的所有操作,那麼使用者在視覺上,就不會產生卡頓的感覺;反之,如果操作的邏輯過多時,就會掉幀從而使得使用者感覺到卡頓。特別的需要頻繁重新整理的介面上,如遊戲(60FPS以上),就會不斷阻塞主執行緒,從而導致介面卡頓。而Android提供了SurfaceView來解決這種情況。

二、SurfaceView和View的不同之處

ViewSurfaceView
適用於主動更新適用於被動重新整理
在主執行緒中進行畫面更新通常通過一個子執行緒來進行畫面更新
繪圖中沒有使用雙緩衝機制在底層實現中就實現了雙緩衝機制

比較了上面的不同之處,顯然可以發現,如果一個View需要頻繁的重新整理,或者在重新整理時資料處理量大(可能引起卡頓),可以考慮使用SurfaceView來替代View。

三、SurfaceView的基本使用

SurfaceView在使用的過程中,有一套模板程式碼,對於大部分的SurfaceView繪圖操作而言都可以套用,因此SurfaceView在使用過程中並不難。

其中值得注意的幾個點:。

兩個介面

SurfaceHolder.CallBack

Runnable

第一個介面中需要實現的方法分別對應於SurfaceView的生命週期,即建立、改變和銷燬。具體程式碼如下:

//Surface的生命週期
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    
}

而第二介面需要實現run方法,用於在子執行緒中進行draw操作。

由於SurfaceView的基本操作比較簡單,這邊就直接給出了它的一個模板程式碼

package com.pignet.surfaceviewdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
 * Created by DB on 2017/6/9.
 */
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback,Runnable{
    private SurfaceHolder mHolder;
    private Canvas mCanvas;
    private boolean mIsDrawing;
    //構造方法
    public SurfaceViewTemplate(Context context) {
        super(context);
        initView();
    }
    public SurfaceViewTemplate(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    private void initView() {
        mHolder=getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
    }
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing=true;
        new Thread(this).start();
        
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing=false;
    }
    @Override
    public void run() {
        while (mIsDrawing){
            draw();
            //通過執行緒休眠以控制重新整理速度
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private void draw() {
        try {
            mCanvas=mHolder.lockCanvas();
            //初始化畫布並在畫布上畫一些東西
        }catch (Exception e){
        }finally {
            //判斷畫布是否為空,從而避免黑畫面情況
            if(mCanvas!=null){
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }
}

下面結合一個具體的範例,展現SurfaceView在繪圖中的效果(繪圖板,即通過監聽觸控事件完成內容的繪製)。

package com.pignet.surfaceviewdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
 * Created by DB on 2017/6/9.
 */
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback,Runnable {
    private  static  final  String TAG="SurfaceView";
    //SurfaceHolder
    private SurfaceHolder mHolder;
    //用於繪圖的Canvas
    private Canvas mCanvas;
    //子執行緒標誌位
    private boolean mIsDrawing;
    //畫筆
    private Paint mPaint;
    //路徑
    private Path mPath;
    public SurfaceViewTemplate(Context context) {
        super(context);
        initView();
    }
    public SurfaceViewTemplate(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }
    private void initView() {
        mHolder = getHolder();
        //新增回撥
        mHolder.addCallback(this);
        mPath=new Path();
        //初始化畫筆
        mPaint=new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(6);
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
    }
    //Surface的生命週期
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing=true;
        new Thread(this).start();
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing=false;

    }
    @Override
    public void run() {
        long start =System.currentTimeMillis();
        while(mIsDrawing){
            draw();
            long end = System.currentTimeMillis();
            if(end-start<100){
                try{
                    Thread.sleep(100-end+start);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private void draw() {
        try{
            //鎖定畫布並返回畫布物件
            mCanvas=mHolder.lockCanvas();
            //接下去就是在畫布上進行一下draw
            mCanvas.drawColor(Color.WHITE);
            mCanvas.drawPath(mPath,mPaint);
        }catch (Exception e){
        }finally {
            //當畫布內容不為空時,才post,避免出現黑畫面的情況。
            if(mCanvas!=null)
                mHolder.unlockCanvasAndPost(mCanvas);
        }
    }
    /**
     * 繪製觸控滑動路徑
     * @param event MotionEvent
     * @return true
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x=(int) event.getX();
        int y= (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "onTouchEvent: down");
                mPath.moveTo(x,y);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "onTouchEvent: move");
                mPath.lineTo(x,y);
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "onTouchEvent: up");
                break;
        }
        return true;
    }
    /**
     * 清屏
     * @return true
     */
    public boolean reDraw(){
        mPath.reset();
        return true;
    }
}

效果圖:

四、tips:

SurfaceView和View一大不同就是SurfaceView是被動重新整理的,但我們可以控制重新整理的影格率,而View並且通過invalidate方法通知系統來主動重新整理介面的,但是View的重新整理是依賴於系統的VSYSC訊號的,其影格率並不受控制,而且因為UI執行緒中的其他一些操作會導致掉幀卡頓。而對於SurfaceView而言,它是在子執行緒中繪製圖形,根據這一特性即可控制其顯示影格率,通過簡單地設定休眠時間,即可,並且由於在子執行緒中,一般不會引起UI卡頓。

Thread.sleep(50);即可以控制1s內重新整理20次

SurfaceView的雙緩衝機制:即對於每一個SurfaceView物件而言,有兩個獨立的graphic buffer。在Android SurfaceView的雙緩衝機制中是這樣實現的:

在Buffer A中繪製內容,然後讓螢幕顯示Buffer A;在下一個迴圈中,在Buffer B中繪製內容,然後讓螢幕顯示Buffer B,如此往復。而由於這個雙緩衝機制的存在,可能會引起閃屏現象,。在第一個"lockCanvas-drawCanvas-unlockCanvasAndPost "迴圈中,更新的是buffer A的內容;到下一個"lockCanvas-drawCanvas-unlockCanvasAndPost"迴圈中,更新的是buffer B的內容。 如果buffer A與buffer B中某個buffer內容為空,當螢幕輪流顯示它們時,就會出現畫面黑畫面閃爍現象。

解決方法

出現黑畫面是因為buffer A與buffer B中一者內容為空,而且為空的一方還被post到了螢幕。於是有兩種解決思路:

1.不讓空buffer出現:每次向一個buffer寫完內容並post之後,順便用這個buffer的內容填充另一個buffer。這樣能保證兩個 buffer的內容是同步的,缺點是做了無用功,耗費效能。

2.不post空buffer到螢幕:當準備更新內容時,先判斷內容是否為空,只有非空時才啟動"lockCanvas-drawCanvas-unlockCanvasAndPost"這個流程。(上述模板和範例中即採用了這個方法)

以上就是Android系統view與SurfaceView的基本使用及區別分析的詳細內容,更多關於Android view與SurfaceView使用區別的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com