SurfaceView詳解和使用


雙緩沖機制

不管是什么操作系統,都有個“圖像數據緩沖區”,存放顏色數據,每隔一段時間,把這些顏色數據投射到顯示器上,我們就看到了各種各樣的畫面。

對於應用程序來說,只需要把想要展示的內容存放到“圖像數據緩沖區”就可以了,這個操作也基本是系統幫我們做了。這樣的模式有個問題就是:如果系統每16ms投射一次圖像數據,而我們的UI顯示此時還沒繪制完成,就只能顯示一部分,剩下的一部分就是上一次是圖像。

對於這種情況,系統一般會用背景色來填充未完成的部分,保證不會出現圖像殘影,但是這樣又會導致閃屏:一個完整的圖像經過多次投射才顯示完,每次都有一部分從背景色變為圖像,這樣的體驗就是閃屏。

解決方法就是雙緩沖機制,對於繪制復雜的圖像,比如游戲、視頻等,會創建第二個“圖像數據緩沖區”。繪制完成前,圖像數據都是放在第二個緩沖區,繪制完成后,才會一次性復制到另一個緩沖區,這樣就完美解決了閃屏的問題。
SurfaceView簡介

一、Android圖層關系
Android中,每個Activity對應一個顯示圖層,這個Activity里的所有控件都是顯示在這個圖層中。而SurfaceView跟其它控件不一樣,它單獨對應一個圖層,跟Activity平級。但是在層次排列上,SurfaceView的圖層在Activity圖層的下面,所以需要在Activity圖層上顯示一塊透明的區域,用於顯示下面的SurfaceView圖層。

那么,Activity怎么知道這塊透明區域位置在哪、寬高多少呢?上面說的是顯示圖層不一樣,但是布局的時候,SurfaceView依然是Activity的RootView的Child,所有的measure、layout等流程都是和其它控件一樣的。

二、SurfaceView和普通View的區別
普通的View,onDraw()方法是在主線程中調用的,有兩種情況下會被調用。一種是畫面有變化(滑動、轉場等)時,系統通知調用,另一種是主動調用invalidate()告訴系統,然后再等待系統的通知。總的來說,是一種被動等待刷新,不能控制刷新頻率。

而SurfaceView不在Activity那個圖層,繪制、刷新完全不受約束,想什么時候繪制、刷新都可以自由控制。

總的來說,SurfaceView主要就是繪制、刷新和顯示過程跟普通View不一樣而已,其他的都一樣。緩存圖像數據、顯示等等這些事都是系統底層做好的,我們只需要關注繪制、刷新過程就行,SurfaceView開放的幾個接口也基本都是繪制、刷新的。
SurfaceView相關接口

一、SurfaceView的接口

    public SurfaceHolder getHolder()
    SurfaceView只有一個常用接口getHolder(),返回一個SurfaceHolder對象,通過這個對象管理圖層的繪制、刷新等操作。

二、SurfaceHolder的接口

    void addCallback(SurfaceHolder.Callback callback)
    通過這個方法添加一個callback,監聽Surface圖層的生命周期

    void setFixedSize(int width, int height)
    通過這個方法,設置Surface圖層的大小,設置之后是不可變的。這個方法需要在UI線程調用。

    void setSizeFromLayout()
    設置Surface圖層的大小根據容器(即Activity)的改變而改變,通過callback可以監聽Surface的改變。這個方法也需要在UI線程調用。

    Canvas lockCanvas() 和 void unlockCanvasAndPost(Canvas canvas)
    通過lockCanvas()獲取Surface對應的Canvas,進行繪制操作,繪制完成后,通過unlockCanvasAndPost()提交,提交完成后繪制的內容就會顯示在屏幕上。注意,下次再次獲取Canvas時,上一次繪制的內容可能丟失了,需要重繪。

    Rect getSurfaceFrame()
    返回Surface對應的Rect,但是坐標left、top的值總是0。注意:不要修改這個Rect。

    Surface getSurface()
    直接訪問Surface,如有有需要的話,可以用這個方法獲取Surface,直接進行操作。

SurfaceView自定義繪制demo

public class SurfaceViewDraw extends SurfaceView implements SurfaceHolder.Callback,Runnable{
    private static final String TAG = "SurfaceViewDraw";
    private SurfaceHolder mHolder;
    private boolean surfaceAvailable;

    private Canvas mCanvas;
    private Paint mPaint;
    private Path mPath;

    private Thread mDrawThread;

    public SurfaceViewDraw(Context context) {
        super(context);
        initView();
    }

    public SurfaceViewDraw(Context context, AttributeSet attrs) {
        super(context, attrs);
        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);
        setKeepScreenOn(true);
    }

    private void draw() {
        try {
            //鎖定畫布並返回畫布對象
            mCanvas = mHolder.lockCanvas();
            mCanvas.drawColor(Color.WHITE);
            mCanvas.drawPath(mPath, mPaint);
        } catch (Exception e) {
        } finally {
            //當畫布內容不為空時才提交顯示
            if (mCanvas != null)
                mHolder.unlockCanvasAndPost(mCanvas);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 在主線程中接收用戶的觸摸事件,記錄下來。在子線程中,每隔100毫秒執行一次draw()方法,根據主線程中的觸摸繪制圖像
        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;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        surfaceAvailable = true;

        // surface創建完成后,就啟動子線程,循環進行繪制操作。
        if(null == mDrawThread){
            mDrawThread = new Thread(this);
        }
        mDrawThread.start();
    }

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

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

    @Override
    public void run() {
        // 每隔100毫秒,執行一次draw()方法,即刷新頻率為100毫秒
        while (surfaceAvailable) {
            draw();

            // 嚴格一點的話應該把繪制時間算進去,這里主要演示用法,就不搞那么復雜了
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107

SurfaceView在播放視頻中的使用

使用MediaPlayer播放音視頻,通過setDisplay()方法設置一個Surface,讓視頻畫面顯示出來。這里面涉及到兩個過程,一個是player本身的播放操作、控制,一個是Surface的初始化操作,這兩個過程是相互獨立的,互不影響,唯一的交集就是Surface創建完成后調用player的setDisplay()方法將Surface設置進去而已。

下面使用的player是簡單封裝過的,下面也會貼出主要代碼,了解MediaPlayer詳情可以看 - MediaPlaer詳解和使用

SurfaceView在播放視頻中的使用如下:

public class PlayerActivity extends Activity {
    private static final String TAG = "PlayerActivity";
    private static final String VIDEO_DIR_0 = "/storage/sdcard0/zzzccc/videotest/video00.mp4";
    private MyPlayer mPlayer;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_surfaceviewplayer);

        // 播放視頻。MyPlayer的代碼后面貼出
        mPlayer = new MyPlayer();
        mPlayer.play(this, Uri.parse(VIDEO_DIR_0));

        // 初始化Surface,創建成功后,調用player的setDisplay()方法設置Surface
        SurfaceView surfaceView = findViewById(R.id.sv_player);
        SurfaceHolder holder = surfaceView.getHolder();
        holder.setFormat(PixelFormat.RGB_888);
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                mPlayer.setDisplay(holder);
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                // 這里可以實時監聽視頻界面的變化
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                // 界面不可見時就會銷毀,比如Activity的onStop()方法。這里不需要做額外的操作,player會自動處理
            }
        });
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

MyPlayer代碼:

public class MyPlayer implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
    private MediaPlayer mPlayer;
    private boolean hasPrepared;

    private void initIfNecessary() {
        if (null == mPlayer) {
            mPlayer = new MediaPlayer();
            mPlayer.setOnErrorListener(this);
            mPlayer.setOnCompletionListener(this);
            mPlayer.setOnPreparedListener(this);
        }
    }

    public void play(Context context, Uri dataSource) {
        hasPrepared = false; // 開始播放前講Flag置為不可操作
        initIfNecessary(); // 如果是第一次播放/player已經釋放了,就會重新創建、初始化
        try {
            mPlayer.reset(); // 如果不是第一次播放,最好調一下reset()
            mPlayer.setDataSource(context, dataSource); // 設置曲目資源
            mPlayer.prepareAsync(); // 異步的准備方法
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        // release()會釋放player、將player置空,所以這里需要判斷一下
        if (null != mPlayer && hasPrepared) {
            mPlayer.start();
        }
    }

    public void pause() {
        if (null != mPlayer && hasPrepared) {
            mPlayer.pause();
        }
    }

    public void seekTo(int position) {
        if (null != mPlayer && hasPrepared) {
            mPlayer.seekTo(position);
        }
    }

    public void setDisplay(SurfaceHolder holder) {
        if (null != mPlayer) {
            mPlayer.setDisplay(holder);
        }
    }

    public void release() {
        hasPrepared = false;
        mPlayer.stop();
        mPlayer.release();
        mPlayer = null;
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        hasPrepared = true; // 准備完成后回調到這里
        start();
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        hasPrepared = false;
        // 通知調用處,調用play()方法進行下一個曲目的播放
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        hasPrepared = false;
        return false;
    }
}
---------------------
作者:thinkreduce
來源:CSDN
原文:https://blog.csdn.net/u014606081/article/details/79963733
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM