天天看點

Android基礎 -- SurfaceView

一、什麼是SurfaceView

普通的View是通過Android系統發出的VSYNC資訊來進行螢幕的繪制,但是在一些需要頻繁重新整理的場景,如直播、遊戲等,如果執行了很多操作,就會導緻繪制間隔逾時,也就是使用者感覺的卡頓,SurfaceView就是為了應對這樣的場景而誕生的。SurfaceView繼承自View,擁有獨立的繪制表面,即它不與其父View共享同一個繪制表面,可以單獨在一個線程進行繪制,并不會占用主線程資源。

二、SurfaceView與View的對比

View适用于主動更新的場景,而SurfaceView适用于被動更新的場景,例如頻繁的重新整理。

View在主線程中對畫面進行重新整理,而SurfaceView通常會通過一個子線程來進行頁面的重新整理。

View在繪制時沒有使用雙緩沖機制,而SurfaceView在底層實作機制中就已經實作了雙緩沖機制。

三、SurfaceView的應用

基礎模版,根據具體需求優化拓展。

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable{

    private SurfaceHolder surfaceHolder;
    private Canvas canvas;
    //子線程繪制标記
    private volatile boolean isDrawing;

    public MySurfaceView(Context context) {
        super(context);
        init();
    }

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

    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);
        setFocusable(true);
//        setFocusableInTouchMode(true);
//        setKeepScreenOn(true);

    }


    //當SurfaceView被建立的時候被調用
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isDrawing = true;
        new Thread(this).start();
    }

    //當SurfaceView的視圖發生改變,比如橫豎屏切換時,這個方法被調用
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    //當SurfaceView被銷毀的時候,比如不可見了,會被調用
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        isDrawing = false;
        surfaceHolder.removeCallback(this);
    }

    @Override
    public void run() {
        while (isDrawing) {
            draw();
        }
    }

    private void draw() {
        try {
            canvas = surfaceHolder.lockCanvas();
            //執行具體的繪制操作

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (canvas != null) {
                surfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    }
}
           

1)SurfaceView必須實作SurfaceHolder的Callback接口,主要是三個方法,用來監聽SurfaceView的狀态

surfaceCreated:SurfaceView被建立時調用,一般執行初始化操作,例如設定繪制線程的标記位,建立用于繪制的子線程等。

2)surfaceChanged:當SurfaceView狀态發生改變時,例如尺寸、格式、螢幕旋轉等。

3)surfaceDestroyed:surfaceDestroyed被調用後,就不能再對Surface對象進行任何操作,是以我們需要在surfaceDestroyed方法中将繪制的子線程停掉。

這三個回調方法有點像Activity的生命周期回調。對于較為複雜的繪制場景,開啟子線程避免阻塞UI線程,通過SurfaceHolder的loackCanvas方法擷取Canvas對象來進行具體的繪制操作,此時Canvas對象被目前線程鎖定,繪制完成後通過SurfaceHolder的unlockCanvasAndPost方法送出繪制結果并釋放Canvas對象,這也就是雙緩沖機制的展現,同時在使用過程中要注意線程安全的問題。

四、雙緩沖機制

因為CPU通路記憶體的速度要遠快于通路螢幕的速度,如果繪制大量複雜圖像時,每次都是在記憶體中讀取圖形資料然後繪制到螢幕,就會造成多次的螢幕通路,進而導緻低效繪制及可能的卡頓,這就和CPU和記憶體之間還需要有三級緩存一樣,需要提高效率。

1.第一層緩沖

在繪制時不用向上面講的那樣一個一個的繪制,而采用先在記憶體中将所有的圖形都繪制到一個Bitmap對象上,然後一次性将記憶體中的Bitmap繪制到螢幕,進而提高繪制效率。Android中View的onDraw方法已經實作了這一層緩沖,onDraw方法不是繪制一點顯示一點,而是繪制完成之後一次性顯示到螢幕。

2.第二層緩沖

onDraw方法的Canvas對象和螢幕是關聯的,而onDraw方法是運作在UI線程中,如果要繪制的圖像過于複雜,則有可能導緻應用卡頓,甚至ANR。是以我們可以先建立一個臨時的Canvas對象,将圖像先繪制到這個臨時的Canvas對象中,然後在将這個臨時的Canvas對象中的内容,其實也是一個Bitmap,通過drawBitmap方法繪制到onDraw方法中的Canvas對象,這樣也是一個Bitmap的拷貝過程,比直接繪制效率要高,可以減少對UI線程的阻塞。

五、SurfaceView源碼分析

與SurfaceView緊密相關的還有兩個類,分别是SurfaceHolder和Surface,它們三個其實就是典型的MVC模式,SurfaceView對應的View層,SurfaceHolder對應controller接口,Surface對應Model層,裡面持有Canvas,儲存着繪制資料。

1.SurfaceHolder

public interface SurfaceHolder {

    ...

    public interface Callback {

        public void surfaceCreated(SurfaceHolder holder);

        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height);

        public void surfaceDestroyed(SurfaceHolder holder);
    }

    public interface Callback2 extends Callback {
        public void surfaceRedrawNeeded(SurfaceHolder holder);
    }

    public void addCallback(Callback callback);

    public void removeCallback(Callback callback);
    
    public Canvas lockCanvas();

    public Canvas lockCanvas(Rect dirty);

    public void unlockCanvasAndPost(Canvas canvas);

    public Surface getSurface();
    
    ...
}
           

SurfaceHolder的接口可以分為兩類

一類是Callback接口,也就是上面提到的三個回調方法,用于監聽SurfaceView的狀态。

另一類主要用于和Surface和SurfaceView互動,比如lockCanvas和unlockCanvasAndPost方法用于擷取Canvas以及送出繪制結果等。

2.SurfaceView

public class SurfaceView extends View {
    ...

    final Surface mSurface = new Surface(); 
    final ReentrantLock mSurfaceLock = new ReentrantLock();
    
    private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {

        private static final String LOG_TAG = "SurfaceHolder";

        ...

        @Override
        public void addCallback(Callback callback) {
            synchronized (mCallbacks) {
                // This is a linear search, but in practice we'll
                // have only a couple callbacks, so it doesn't matter.
                if (mCallbacks.contains(callback) == false) {
                    mCallbacks.add(callback);
                }
            }
        }

        @Override
        public void removeCallback(Callback callback) {
            synchronized (mCallbacks) {
                mCallbacks.remove(callback);
            }
        }
        
        @Override
        public Canvas lockCanvas() {
            return internalLockCanvas(null);
        }

        @Override
        public Canvas lockCanvas(Rect inOutDirty) {
            return internalLockCanvas(inOutDirty);
        }

        private final Canvas internalLockCanvas(Rect dirty) {
            mSurfaceLock.lock();

            Canvas c = null;
            if (!mDrawingStopped && mWindow != null) {
                try {
                    c = mSurface.lockCanvas(dirty);
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exception locking surface", e);
                }
            }

            if (c != null) {
                mLastLockTime = SystemClock.uptimeMillis();
                return c;
            }

            long now = SystemClock.uptimeMillis();
            long nextTime = mLastLockTime + 100;
            if (nextTime > now) {
                try {
                    Thread.sleep(nextTime-now);
                } catch (InterruptedException e) {
                }
                now = SystemClock.uptimeMillis();
            }
            mLastLockTime = now;
            mSurfaceLock.unlock();

            return null;
        }

        @Override
        public void unlockCanvasAndPost(Canvas canvas) {
            mSurface.unlockCanvasAndPost(canvas);
            mSurfaceLock.unlock();
        }

        @Override
        public Surface getSurface() {
            return mSurface;
        }

        @Override
        public Rect getSurfaceFrame() {
            return mSurfaceFrame;
        }
    };
    
    ...
}
           

SurfaceView雖然繼承自View,但是其實和View有很大的不同,除了上面的幾點特性外,在底層也有很大的不同,包括擁有自己獨立的繪圖表面等。從上面的源碼可以看出,SurfaceHolder的lockCanvas方法實際上調用的是Surface的lockCanvas方法,傳回的是Surface中的Canvas,并且加入了一個可重入鎖,是以繪制過程中隻能繪制完一貞内容并送出更改以後才會釋放Canvas,也就是才能繼續下一貞的操作。

3.Surface

public class Surface implements Parcelable {

    final Object mLock = new Object(); // protects the native state
    private final Canvas mCanvas = new CompatibleCanvas();
    
    ...
    
    public Canvas lockCanvas(Rect inOutDirty)
            throws Surface.OutOfResourcesException, IllegalArgumentException {
        synchronized (mLock) {
            checkNotReleasedLocked();
            if (mLockedObject != 0) {
                throw new IllegalArgumentException("Surface was already locked");
            }
            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
            return mCanvas;
        }
    }
    
    public void unlockCanvasAndPost(Canvas canvas) {
        synchronized (mLock) {
            checkNotReleasedLocked();

            if (mHwuiContext != null) {
                mHwuiContext.unlockAndPost(canvas);
            } else {
                unlockSwCanvasAndPost(canvas);
            }
        }
    }

    private void unlockSwCanvasAndPost(Canvas canvas) {
        if (canvas != mCanvas) {
            throw new IllegalArgumentException("canvas object must be the same instance that "
                    + "was previously returned by lockCanvas");
        }
        if (mNativeObject != mLockedObject) {
            Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
                    Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
                    Long.toHexString(mLockedObject) +")");
        }
        if (mLockedObject == 0) {
            throw new IllegalStateException("Surface was not locked");
        }
        try {
            nativeUnlockCanvasAndPost(mLockedObject, canvas);
        } finally {
            nativeRelease(mLockedObject);
            mLockedObject = 0;
        }
    }
    
    ...
}
           

Surface實作了Parcelable接口,因為它需要在程序間以及本地方法間傳輸。Surface中建立了Canvas對象,用于執行具體的繪制操作

六、簡單應用

public class CircleMoveView extends SurfaceView implements SurfaceHolder.Callback {

    private SurfaceHolder holder;

    private Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (flag) {
                draw();
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    private boolean flag;

    private Canvas canvas;

    private Paint paint;

    private int x = 100, y = 100, r = 50;

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

    public CircleMoveView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleMoveView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        holder = getHolder();
        holder.addCallback(this);
        paint = new Paint();
        paint.setColor(Color.RED);
        setFocusable(true);
    }

    private void draw() {
        canvas = holder.lockCanvas(); //擷取canvas對象 這裡的canvas對象其實是surface裡的canvas對象
        canvas.drawRGB(0, 0, 0);
        canvas.drawCircle(x, y, r, paint);
        holder.unlockCanvasAndPost(canvas);//完成繪制,把畫布顯示在螢幕上 這裡是第一層緩沖 把surface裡的canvas直接拷貝到目前canvas
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.d("TAG", "surfaceCreated");
        flag = true;
        thread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.d("TAG", "surfaceChanged");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.d("TAG", "surfaceDestroyed");
        flag = false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        x = (int) event.getX();
        y = (int) event.getY();
        return true;
    }
}