注:本文基於Android 10源碼,爲了文章的簡潔性,引用源碼的地方可能有所刪減。文中內容若有錯誤歡迎指出,共同進步!以爲不錯的留個贊再走哈~git
上一篇 Android-Surface之建立流程及軟硬件繪製 介紹了一下 Surface 的建立流程以及 Android 軟硬件繪製的流程,能夠將流程總結爲下圖:github
這篇文章再看看在 View 繪製過程當中所用到的雙緩衝技術,雙緩衝的使用範圍很是普遍,好比說在屏幕圖像顯示的時候就應用到了雙緩衝 -- 分爲屏幕前緩衝區和屏幕後緩衝區,此外還有三緩衝的概念...這篇文章主要看看 View 在繪製的過程當中是怎麼使用雙緩衝的。canvas
通常來講將雙緩衝用到的兩塊緩衝區稱爲 -- 前緩衝區(front buffer) 和 後緩衝區(back buffer)。顯示器顯示的數據來源於 front buffer 前緩存區,而每一幀的數據都繪製到 back buffer 後緩存區,在 Vsync 信號到來後會交互緩存區的數據(指針指向),這時 front buffer 和 back buffer 的稱呼及功能倒轉。緩存
經過以前 Android-Surface之建立流程及軟硬件繪製 的解析知道軟件繪製可分爲三個步驟:markdown
雙緩衝的解析能夠從 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:
Surface.unlockAndPost:
由以前的解析能夠知道硬件繪製最終會調用 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 是一種較之 TextView, Button 等更爲特殊的 View, 它不與其宿主的 Window 共享一個 Surface, 而是有本身的獨立 Surface。而且它能夠在一個獨立的線程中繪製 UI。所以 SurfaceView 通常用來實現比較複雜的圖像或動畫/視頻的顯示。
這裏插入一個內容,關於 View 爲啥不能在子線程中操做 UI 的話能夠看看 ViewRootImpl.checkThread 這個方法,參考 ViewRootImpl.checkThread 方法 其實就是由於每次操做 UI 都會去 check 線程,固然前提是 ViewRootImpl 已經被實例化了~
通常來講,每一個 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 圖形系統中涉及到的這幾篇文章總體總結一下,若有遺漏的內容有時間也會接着補充。源碼解讀可能會存在錯誤的地方,有發現的朋友歡迎指正,一鍵三連哈!