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;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章