Android-Surface之雙緩衝及SurfaceView解析

概述

注:本文基於Android 10源碼,爲了文章的簡潔性,引用源碼的地方可能有所刪減。文中內容若有錯誤歡迎指出,共同進步!以爲不錯的留個贊再走哈~git

上一篇 Android-Surface之建立流程及軟硬件繪製 介紹了一下 Surface 的建立流程以及 Android 軟硬件繪製的流程,能夠將流程總結爲下圖:github

Android 軟硬件繪製流程

這篇文章再看看在 View 繪製過程當中所用到的雙緩衝技術,雙緩衝的使用範圍很是普遍,好比說在屏幕圖像顯示的時候就應用到了雙緩衝 -- 分爲屏幕前緩衝區和屏幕後緩衝區,此外還有三緩衝的概念...這篇文章主要看看 View 在繪製的過程當中是怎麼使用雙緩衝的。canvas

雙緩衝(View繪製過程)

通常來講將雙緩衝用到的兩塊緩衝區稱爲 -- 前緩衝區(front buffer) 和 後緩衝區(back buffer)。顯示器顯示的數據來源於 front buffer 前緩存區,而每一幀的數據都繪製到 back buffer 後緩存區,在 Vsync 信號到來後會交互緩存區的數據(指針指向),這時 front buffer 和 back buffer 的稱呼及功能倒轉。緩存

軟件繪製中的雙緩衝

經過以前 Android-Surface之建立流程及軟硬件繪製 的解析知道軟件繪製可分爲三個步驟:markdown

  1. Surface.lockCanvas: 會調用到 Native 層的 Surface.lock 方法
  2. View.draw: 將繪製數據寫入緩存區
  3. Surface.unlockCanvasAndPost: 會調用到 Native 層 Surface.unlockAndPost 方法

雙緩衝的解析能夠從 Native 層的 Surface.lock 方法開始看起:app

status_t Surface::lock(ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds) {
    ANativeWindowBuffer* out;
    // 經過生產者從 QueueBuffer 隊列中取出一塊空閒的圖形緩存區--GraphicBuffer
    status_t err = dequeueBuffer(&out, &fenceFd);
    // 將 GraphicBuffer 賦值給後緩存區 backBuffer
    sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
    const Rect bounds(backBuffer->width, backBuffer->height);
    // 計算新的髒區--1處圖
    Region newDirtyRegion;
    if (inOutDirtyBounds) {
        // App 經過調用 lockCanvas(Rect inOutDirty) 傳遞了一個髒區
        // 則將傳入的 inOutDirty 做爲新的髒區
        newDirtyRegion.set(static_cast<Rect const&>(*inOutDirtyBounds));
        newDirtyRegion.andSelf(bounds);
    } else {// 不然將後緩存區大小做爲髒區
        newDirtyRegion.set(bounds);
    }
    // 將正在顯示的 mPostedBuffer 緩存區賦值給 frontBuffer 前緩存區
    const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
    // 是否須要將前緩存區拷貝到後緩存區
    // 前緩存區有內容 && 先後緩存區的長寬及格式同樣
    // 第一次繪製時 frontBuffer 是沒內容的
    const bool canCopyBack = (frontBuffer != 0 &&
            backBuffer->width  == frontBuffer->width &&
            backBuffer->height == frontBuffer->height &&
            backBuffer->format == frontBuffer->format);

    if (canCopyBack) {
        // 能夠拷貝時--2處圖
        // copy the area that is invalid and not repainted this round
        const Region copyback(mDirtyRegion.subtract(newDirtyRegion));
        if (!copyback.isEmpty()) {
            copyBlt(backBuffer, frontBuffer, copyback, &fenceFd);
        }
    } else {
        // 不能拷貝時,後緩存區直接取新髒區的區域,確保重繪整個區域
        newDirtyRegion.set(bounds);
        mDirtyRegion.clear();
        Mutex::Autolock lock(mMutex);
        for (size_t i=0 ; i<NUM_BUFFER_SLOTS ; i++) {
            mSlots[i].dirtyRegion.clear();
        }
    }
    // 鎖定 backBuffer
    status_t res = backBuffer->lockAsync(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                newDirtyRegion.bounds(), &vaddr, fenceFd);
    if (res != 0) {
        err = INVALID_OPERATION;
    } else {
        // 將後緩存區賦值給 mLockedBuffer
        mLockedBuffer = backBuffer;
        outBuffer->width  = backBuffer->width;
        outBuffer->height = backBuffer->height;
        outBuffer->stride = backBuffer->stride;
        outBuffer->format = backBuffer->format;
        outBuffer->bits   = vaddr;
    }
    return err;
}
複製代碼

上圖的註釋都比較清晰了,看一下上面標數字的 1 和 2 處邏輯:ide

軟件繪製雙緩衝

在拷貝後,後緩存區淺綠色的部分就是要重繪的區域,而綠色區域是以前前緩存區顯示的內容,與髒區相交的部分要重繪,未相交的區域則不須要重繪,下次顯示時接着使用該區域便可。oop

接下來看看 Surface.unlockAndPost 方法,其中 mLockedBuffer 在 lock 方法中被賦值爲 backBuffer:post

status_t Surface::unlockAndPost() {
    int fd = -1;
    // 解鎖 mLockedBuffer
    status_t err = mLockedBuffer->unlockAsync(&fd);
    // 將繪製後的緩存區入隊列,等待被合成顯示
    err = queueBuffer(mLockedBuffer.get(), fd);
    // 賦值給 mPostedBuffer,表明要被顯示的數據
    mPostedBuffer = mLockedBuffer;
    mLockedBuffer = 0;
    return err;
}
複製代碼

小結動畫

Surface.lock:

  1. 將出隊列的空閒緩存區 GraphicBuffer 賦給後緩存區 backBuffer,將正在顯示的 mPostedBuffer 賦給前緩存區。
  2. 計算新的髒區,並肯定是否須要將前緩存區拷貝到後緩存區,依此計算出後緩存區 backBuffer 的最終數據。而後將 backBuffer 與應用層的 Canvas 關聯,當操做 Canvas 繪圖時會將數據繪製到 backBuffer 上。
  3. 鎖定 backBuffer 且將 backBuffer 指針賦值給 mLockedBuffer。

Surface.unlockAndPost:

  1. 將存有繪製數據的 mLockedBuffer 解鎖並將其賦值給 mPostedBuffer。
  2. 將 mLockedBuffer 入 BufferQueue 隊列,等待被合成顯示,在這裏便至關於交換了先後緩衝區的指針,等到下次繪製時,接着重複上面的步驟。

硬件繪製中的雙緩衝

由以前的解析能夠知道硬件繪製最終會調用 CanvasContext.draw 方法來繪製:

void CanvasContext::draw() {
    SkRect dirty;
    mDamageAccumulator.finish(&dirty);
    Frame frame = mRenderPipeline->getFrame();
    // 計算髒區
    SkRect windowDirty = computeDirtyRect(frame, &dirty);
    // 渲染
    bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
                                      mContentDrawBounds, mOpaque, mWideColorGamut, mLightInfo,
                                      mRenderNodes, &(profiler()));
    // 交換緩存區
    bool didSwap = mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);
    // ...
}
複製代碼

具體的代碼比較複雜(看不太懂了),經過相關的調用大概能夠看出硬件繪製時應該也是存在雙緩衝的,有興趣的話能夠再深刻看看,有大佬知道的歡迎指點!

SurfaceView

概述

SurfaceView 是一種較之 TextView, Button 等更爲特殊的 View, 它不與其宿主的 Window 共享一個 Surface, 而是有本身的獨立 Surface。而且它能夠在一個獨立的線程中繪製 UI。所以 SurfaceView 通常用來實現比較複雜的圖像或動畫/視頻的顯示。

這裏插入一個內容,關於 View 爲啥不能在子線程中操做 UI 的話能夠看看 ViewRootImpl.checkThread 這個方法,參考 ViewRootImpl.checkThread 方法 其實就是由於每次操做 UI 都會去 check 線程,固然前提是 ViewRootImpl 已經被實例化了~

  • SurfaceView 在繪圖時實現了雙緩衝機制(獨立的 Surface)
  • 普通 View 在繪圖時會繪製到 Bitmap 中,而後經過其所在 Window 的 Surface 對象實現雙緩衝。

通常來講,每一個 Window 都有對應的 Surface 繪製表面,它在 SurfaceFlinger 服務中對應一個 Layer。而對於存在 SurfaceView 的 Window 來講,它除了本身的 Surface 之外還會有另外一個 SurfaceView 獨有的 Surface 繪製表面,在 SurfaceFlinger 服務中也會存在着兩個 Layer 分別對應它們。查看 SurfaceView 的源碼能夠看到 SurfaceView 相似於 ViewRootImpl 同樣其內部都有一個單獨的 Surface 實例,因而結合以前 Android-Surface之建立流程及軟硬件繪製 的解析,就能理解剛纔對 SurfaceView 的描述了。

SurfaceView 官方註釋有一段: The surface is Z ordered so that it is behind the window holding its SurfaceView; the SurfaceView punches a hole in its window to allow its surface to be displayed.

翻譯一下大致意思是:Surface 是按照 Z 軸順序排列的,SurfaceView 的 Surface 位於其宿主窗口的 Surface 後面;SurfaceView 在其窗口上打一個孔,以顯示其 Surface。這個孔其實是 SurfaceView 在其宿主窗口上設置了一塊透明區域。

示例

接下來看一個使用 SurfaceView 的示例:

class MySurfaceView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) :
    SurfaceView(context, attrs, defStyleAttr), SurfaceHolder.Callback, Runnable {

    companion object {
        private const val TAG = "MySurfaceView"
    }

    private val surfaceHolder: SurfaceHolder = holder
    private var canvas: Canvas? = null

    @Volatile
    private var canDoDraw = false
    private val drawThread = Thread(this)
    private val lock = Object()

    private var xx = 0f
    private var yy = 400f
    private val path = Path()
    private val paint = Paint()

    constructor(context: Context?) : this(context, null, 0)
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)

    init {
        surfaceHolder.addCallback(this)
        isFocusable = true
        isFocusableInTouchMode = true
        keepScreenOn = true
        path.moveTo(xx, yy)
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 10f
        paint.color = Color.RED
        drawThread.start()
    }

    override fun surfaceCreated(holder: SurfaceHolder?) {
        Log.d(TAG, "surfaceCreated")
        setCanDraw(true)
    }

    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
        Log.d(TAG, "surfaceChanged")
    }

    override fun surfaceDestroyed(holder: SurfaceHolder?) {
        Log.d(TAG, "surfaceDestroyed")
        canDoDraw = false
        // 注意當APP到後臺時會 destroy Surface, 回到前臺會從新調用 surfaceCreated
        // 所以這裏不能移除回調,不然會黑屏
        // surfaceHolder.removeCallback(this)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        Log.d(TAG, "onTouchEvent")
        setCanDraw(!canDoDraw)
        return super.onTouchEvent(event)
    }

    private fun setCanDraw(canDraw: Boolean) {
        if (canDraw) {
            synchronized(lock) {
                try {
                    lock.notifyAll()
                } catch (e: Exception) {
                }
            }
        }
        canDoDraw = canDraw
    }

    override fun run() {
        while (true) {
            synchronized(lock) {
                if (!canDoDraw) {
                    try {
                        lock.wait()
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            }
            draw()
            xx += 1
            yy = (100 * sin(xx * 2 * Math.PI / 180) + 400).toFloat()
            path.lineTo(xx, yy)
        }
    }

    private fun draw() {
        try {
            canvas = surfaceHolder.lockCanvas()
            canvas?.drawColor(Color.WHITE)
            canvas?.drawPath(path, paint)
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            surfaceHolder.unlockCanvasAndPost(canvas ?: return)
        }
    }
}
複製代碼

源碼解析

先看一下 SurfaceHolder 這個接口:

public interface SurfaceHolder {
    public interface Callback {
        // 首次建立 Surface 後會調用此方法,在這個回調裏應該開始繪製任務
        // 只有一個線程能夠繪製到 Surface 中,若是渲染任務將在另外一個線程中進行則不能在此處繪製 Surface
        public void surfaceCreated(SurfaceHolder holder);

        // 當 Surface 結構更改(format or size)後會調用此方法,在這個回調裏應該更新 Surface 的圖像
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);

        // 在 Surface destroy 以前會調用此方法,在這個回調之後不該該嘗試訪問此 Surface
        public void surfaceDestroyed(SurfaceHolder holder);
    }

    public void addCallback(Callback callback);
    public void removeCallback(Callback callback);
    // 是否正在經過 Callback 方法建立 Surface
    public boolean isCreating();
    public void setType(int type); // Sets the surface's type.
    public void setFixedSize(int width, int height);
    public void setSizeFromLayout();
    public void setFormat(int format);
    // Enable or disable option to keep the screen turned on while this surface is displayed.
    public void setKeepScreenOn(boolean screenOn); // 默認 false
    public Canvas lockCanvas();
    public Canvas lockCanvas(Rect dirty);
    default Canvas lockHardwareCanvas() {
        throw new IllegalStateException("This SurfaceHolder doesn't support lockHardwareCanvas");
    }
    public void unlockCanvasAndPost(Canvas canvas);
    public Rect getSurfaceFrame();
    public Surface getSurface();
}
複製代碼

能夠看出 SurfaceHolder 是用來管理 Surface 的類。接下來看下 SurfaceView 的 draw 源碼:

@Override
public void draw(Canvas canvas) {
    if (mDrawFinished && !isAboveParent()) {
        // draw() is not called when SKIP_DRAW is set
        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
            // punch a whole in the view-hierarchy below us
            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
        }
    }
    super.draw(canvas);
}

@Override
protected void dispatchDraw(Canvas canvas) {
    if (mDrawFinished && !isAboveParent()) {
        // draw() is not called when SKIP_DRAW is set
        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
            // punch a whole in the view-hierarchy below us
            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
        }
    }
    super.dispatchDraw(canvas);
}
複製代碼

SurfaceView 方法中 draw 和 dispatchDraw 的參數 canvas 是從宿主的 Surface 中獲取的,所以在該 canvas 上繪製的內容都會出如今宿主的 Surface 上。

因此能夠看到 SurfaceView.draw 和 SurfaceView.dispatchDraw 方法的邏輯是:若是當前 SurfaceView 不是用做宿主窗口面板,則 SurfaceView 在其宿主窗口 Surface 上的操做只是清空 Canvas 區域,由於 SurfaceView 的內容是須要展示在本身單獨的 Surface 上的(像上面的示例同樣,經過其 Surface 拿到一個 Canvas 並在一個獨立線程在其上進行繪製)。

接着看一下 SurfaceView 中 SurfaceHolder 的實現:

public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
    final ArrayList<SurfaceHolder.Callback> mCallbacks = new ArrayList<>();
    final ReentrantLock mSurfaceLock = new ReentrantLock();
    final Surface mSurface = new Surface();

    public SurfaceHolder getHolder() {
        return mSurfaceHolder;
    }

    private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
        @Override
        public void addCallback(Callback callback) {
            synchronized (mCallbacks) { // 添加回調
                if (mCallbacks.contains(callback) == false) {
                    mCallbacks.add(callback);
                }
            }
        }

        @Override
        public void setFixedSize(int width, int height) {
            if (mRequestedWidth != width || mRequestedHeight != height) {
                mRequestedWidth = width;
                mRequestedHeight = height;
                requestLayout(); // 從新 layout
            }
        }

        @Override
        public void setKeepScreenOn(boolean screenOn) {
            runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
        }

        @Override
        public Canvas lockCanvas() {
            return internalLockCanvas(null, false);
        }

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

        @Override
        public Canvas lockHardwareCanvas() {
            return internalLockCanvas(null, true);
        }

        // 鎖定 Surface
        private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
            mSurfaceLock.lock();

            Canvas c = null;
            if (!mDrawingStopped && mSurfaceControl != null) {
                try {
                    if (hardware) { // 硬件渲染
                        c = mSurface.lockHardwareCanvas();
                    } else { // 軟件渲染
                        c = mSurface.lockCanvas(dirty);
                    }
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exception locking surface", e);
                }
            }

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

            // 當返回 null 時使 internalLockCanvas 被調用的間隔超過100ms
            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;
        }

        // ...
    }
}
複製代碼

能夠看出調用 SurfaceHolder 的 lock 和 unlock 系列方法都是調用到了 Surface 中的方法。除了 lockHardwareCanvas 方法,其餘的在 Android-Surface之建立流程及軟硬件繪製 中都已經看過了:

// Surface
public Canvas lockHardwareCanvas() {
    synchronized (mLock) {
        checkNotReleasedLocked();
        if (mHwuiContext == null) {
            mHwuiContext = new HwuiContext(false);
        }
        return mHwuiContext.lockCanvas(nativeGetWidth(mNativeObject), nativeGetHeight(mNativeObject));
    }
}

// HwuiContext
Canvas lockCanvas(int width, int height) {
    if (mCanvas != null) {
        throw new IllegalStateException("Surface was already locked!");
    }
    mCanvas = mRenderNode.start(width, height);
    return mCanvas;
}
複製代碼

能夠看到 HwuiContext.lockCanvas 是使用硬件加速的方式,其調用的 RenderNode.start 以前已經看過了,與之對應的 RenderNode.end 方法是在這裏調用的:

// Surface
public void unlockCanvasAndPost(Canvas canvas) {
    synchronized (mLock) {
        checkNotReleasedLocked();

        if (mHwuiContext != null) { // 不爲空時
            mHwuiContext.unlockAndPost(canvas);
        } else {
            unlockSwCanvasAndPost(canvas);
        }
    }
}

// HwuiContext
void unlockAndPost(Canvas canvas) {
    if (canvas != mCanvas) {
        throw new IllegalArgumentException("canvas object must be the same instance that "
                + "was previously returned by lockCanvas");
    }
    mRenderNode.end(mCanvas);
    mCanvas = null;
    nHwuiDraw(mHwuiRenderer);
}
複製代碼

即 SurfaceView 的繪製兼顧了軟件繪製和硬件加速繪製。另外在 SurfaceView.updateSurface 方法中會更新 Surface 的狀態並將其回調給 SurfaceHolder.Callback 相關方法,具體邏輯便不給出了。

總結

關於 Surface 的解析到這裏就差很少了,後面我會將 Android 圖形系統中涉及到的這幾篇文章總體總結一下,若有遺漏的內容有時間也會接着補充。源碼解讀可能會存在錯誤的地方,有發現的朋友歡迎指正,一鍵三連哈!

相關文章
相關標籤/搜索