我們已經(jīng)分析了驶赏,mWindowSession.addToDisplay 通過WMS.addWindow 我們建立了app與SurfaceFlinger服務(wù)連接。并且通過requestLayout中的relayoutWindow, app請(qǐng)求SurfaceFlinger創(chuàng)建了Surface爷辱。那么接下來录豺,我們?cè)俜治鱿耡pp的視圖是如何被繪制到GraphicFrame上的。這里面牽扯到的View饭弓、Canvas與Surface的關(guān)系双饥,用這篇文章來梳理一下。(流程走的是軟件繪制流程)
之前我們講到requestLayout弟断,開始了view的measure咏花、layout、draw流程阀趴,我們從performDraw開始關(guān)注下最后的視圖繪制工作:
//ViewRootImpl
private void performDraw() {
...
draw(fullRedrawNeeded);
...
}
然后看ViewRootImpl的draw方法:
//ViewRootImpl
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
...
}
這里我們看到昏翰,surface 在這里被接收了,并傳入了drawSoftware刘急。
//ViewRootImpl
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
canvas = mSurface.lockCanvas(dirty); //1.獲取Canvas
...
mView.draw(canvas); //2.通過Canvas繪制視圖
...
surface.unlockCanvasAndPost(canvas); //3.繪制結(jié)束
}
在drawSoftware方法中棚菊,我們重點(diǎn)關(guān)注如上三個(gè)方法:
一、 Surface的lockCanvas函數(shù)
//Surface
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;
}
}
其最終調(diào)用了native函數(shù)nativeLockCanvas
//android_view_Surface.cpp
static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
...
ANativeWindow_Buffer outBuffer;
//從SurfaceFlinger中申請(qǐng)內(nèi)存buffer
status_t err = surface->lock(&outBuffer, dirtyRectPtr);
...
SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
convertPixelFormat(outBuffer.format),
outBuffer.format == PIXEL_FORMAT_RGBX_8888 ?
kOpaque_SkAlphaType : kPremul_SkAlphaType);
//新建了一個(gè)SkBitmap叔汁,并進(jìn)行了一系列設(shè)置
SkBitmap bitmap;
ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
bitmap.setInfo(info, bpr);
if (outBuffer.width > 0 && outBuffer.height > 0) {
bitmap.setPixels(outBuffer.bits);
} else {
// be safe with an empty bitmap.
bitmap.setPixels(NULL);
}
Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
//把這個(gè)bitmap放入Canvas中
nativeCanvas->setBitmap(bitmap);
...
sp<Surface> lockedSurface(surface);
lockedSurface->incStrong(&sRefBaseOwner);
return (jlong) lockedSurface.get();
}
這里主要關(guān)注幾個(gè)點(diǎn):
1.1 surface->lock(&outBuffer, dirtyRectPtr)
調(diào)用了Surface的lock函數(shù)實(shí)際上主要是調(diào)用了Surface的dequeueBuffer窍株,而這個(gè)函數(shù)的主要目的是從SurfaceFlinger中申請(qǐng)GraphicBuffer, 這個(gè)buffer是用來傳遞繪制的元數(shù)據(jù)的。
1.2 GraphicsJNI::getNativeCanvas(env, canvasObj)
構(gòu)造一個(gè)native的Canvas對(duì)象(SKCanvas)攻柠,再返回這個(gè)Canvas對(duì)象球订,java層的Canvas對(duì)象其實(shí)只是對(duì)SKCanvas對(duì)象的一個(gè)簡單包裝,所有繪制方法都是轉(zhuǎn)交給SKCanvas來做瑰钮。
1.3 SkBitmap bitmap
Canvas底層是通過2D圖形引擎skia進(jìn)行圖形繪制的冒滩,SkBitmap是skia中很重要的一個(gè)類,很多畫圖動(dòng)作涉及到SkBitmap浪谴,它封裝了與位圖相關(guān)的一系列操作开睡。那么在這里,bitmap對(duì)下設(shè)置了獲取的內(nèi)存buffer苟耻,對(duì)上關(guān)聯(lián)了Canvas ,即把這個(gè)bitmap放入了Canvas中( nativeCanvas->setBitmap(bitmap) )
總結(jié):Surface的lockCanvas函數(shù)會(huì)通過jni調(diào)用對(duì)應(yīng)的native方法篇恒,本質(zhì)是通過Surface的dequeueBuffer獲取一塊用于存放繪制元數(shù)據(jù)的GraphicBuffer,然后構(gòu)造一個(gè)SKBitmap凶杖,它是繪制的核心, 對(duì)下關(guān)聯(lián)buffer胁艰,對(duì)上關(guān)聯(lián)canvas。
二、mView.draw(canvas)
這其實(shí)就是通過Canvas去實(shí)現(xiàn)具體的繪制腾么。
以TextView的一部分繪制代碼為例:
protected void onDraw(Canvas canvas) {
...
if (dr.mShowing[Drawables.LEFT] != null) {
canvas.save();//坐標(biāo)系的原點(diǎn)奈梳,坐標(biāo)軸方向的信息。
canvas.translate(scrollX + mPaddingLeft + leftOffset,
scrollY + compoundPaddingTop +
(vspace - dr.mDrawableHeightLeft) / 2);
dr.mShowing[Drawables.LEFT].draw(canvas);
canvas.restore();//恢復(fù)Canvas之前保存的狀態(tài)
}
...
}
主要看繪制:
//Canvas.java
public void translate(float dx, float dy) {
native_translate(mNativeCanvasWrapper, dx, dy);
}
//android_graphics_Canvas.cpp
static void translate(JNIEnv*, jobject, jlong canvasHandle, jfloat dx, jfloat dy) {
get_canvas(canvasHandle)->translate(dx, dy);
}
//external/skia/src/core/SkCanvas.cpp
void SkiaCanvas::translate(float dx, float dy) {
mCanvas->translate(dx, dy);
}
//external/skia/src/core/SkCanvas.cpp
void SkCanvas::translate(SkScalar dx, SkScalar dy) {
if (dx || dy) {
this->checkForDeferredSave();
fMCRec->fMatrix.preTranslate(dx,dy);
// Translate shouldn't affect the is-scale-translateness of the matrix.
SkASSERT(fIsScaleTranslate == fMCRec->fMatrix.isScaleTranslate());
FOR_EACH_TOP_DEVICE(device->setGlobalCTM(fMCRec->fMatrix));
this->didTranslate(dx,dy);
}
}
從SkCanvas.cpp的路徑我們可以看出解虱,這已經(jīng)在skia繪制引擎部分攘须,這里就不深究了。只要了解:我們通過java層Canvas封裝的api調(diào)用底層SKCanvas來完成真正的繪制工作就足夠了殴泰。
三于宙、surface.unlockCanvasAndPost(canvas);
//Surface.java
public void unlockCanvasAndPost(Canvas canvas) {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext != null) {
mHwuiContext.unlockAndPost(canvas);
} else {
unlockSwCanvasAndPost(canvas);
}
}
}
正常是調(diào)用unlockSwCanvasAndPost:
//Surface.java
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;
}
}
往下看native方法
//android_view_Surface.cpp
static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject canvasObj) {
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
if (!isSurfaceValid(surface)) {
return;
}
// detach the canvas from the surface
Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
nativeCanvas->setBitmap(SkBitmap());
// unlock surface
status_t err = surface->unlockAndPost();
if (err < 0) {
doThrowIAE(env);
}
}
看下如何解鎖surface:
//Surface.cpp
status_t Surface::unlockAndPost()
{
if (mLockedBuffer == 0) {
ALOGE("Surface::unlockAndPost failed, no locked buffer");
return INVALID_OPERATION;
}
int fd = -1;
status_t err = mLockedBuffer->unlockAsync(&fd);//通過Gralloc模塊,最后是操作的ioctl
ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);
err = queueBuffer(mLockedBuffer.get(), fd);
ALOGE_IF(err, "queueBuffer (handle=%p) failed (%s)",
mLockedBuffer->handle, strerror(-err));
mPostedBuffer = mLockedBuffer;
mLockedBuffer = 0;
return err;
}
我們看到了queueBuffer函數(shù), 而在Surface的queueBuffer函數(shù)中調(diào)用了如下函數(shù):
mGraphicBufferProducer->queueBuffer
這個(gè)函數(shù)最終會(huì)將BufferItem的buffer清除悍汛,通知消費(fèi)者的onFrameAvailable接口限煞。然后消費(fèi)者可以根據(jù)mSlots的序號(hào)再來拿buffer。
//frameworks/native/libs/gui/BufferQueueProducer.cpp
status_t BufferQueueProducer::queueBuffer(int slot,
const QueueBufferInput &input, QueueBufferOutput *output) {
...
item.mGraphicBuffer.clear();
item.mSlot = BufferItem::INVALID_BUFFER_SLOT;
// Call back without the main BufferQueue lock held, but with the callback
// lock held so we can ensure that callbacks occur in order
{
Mutex::Autolock lock(mCallbackMutex);
while (callbackTicket != mCurrentCallbackTicket) {
mCallbackCondition.wait(mCallbackMutex);
}
if (frameAvailableListener != NULL) {
frameAvailableListener->onFrameAvailable(item);
} else if (frameReplacedListener != NULL) {
frameReplacedListener->onFrameReplaced(item);
}
++mCurrentCallbackTicket;
mCallbackCondition.broadcast();
}
...
}
所以整個(gè)過程看起來還是比較簡單的员凝。最后把整個(gè)流程再簡單總結(jié)下署驻,View、Canvas與Surface的關(guān)系也就一目了然了:
Surface通過dequeueBuffer流程(具體操作在此不多贅述)獲取一塊存放繪制數(shù)據(jù)的buffer健霹。
View 在onDraw的時(shí)候旺上,通過傳入的Canvas進(jìn)行繪制。(這里只是一個(gè)繪制的入口而已糖埋,本文是針對(duì)requestLayout流程來講述的宣吱,當(dāng)然你單獨(dú)用Canvas實(shí)現(xiàn)繪制也是一樣的)。
調(diào)用java層的CanvasAPI瞳别,實(shí)際真正負(fù)責(zé)繪制工作的是底層的Skia引擎征候,這里核心類包括SKCanvas(畫家)以及SKBitmap(畫布),繪制好的內(nèi)容放入Surface 通過dequeueBuffer獲取到的GraphicBuffer祟敛。
繪制完畢后疤坝,Surface通過queueBuffer將存放好繪制數(shù)據(jù)的buffer投遞到隊(duì)列中,并通知SurfaceFlinger消費(fèi)馆铁。
參考:
https://blog.csdn.net/kc58236582/article/details/52879698
https://blog.csdn.net/jianpan_zouni/article/details/77649271