Android圖形系統(tǒng)(九)-View湃望、Canvas與Surface的關(guān)系

我們已經(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)系也就一目了然了:

  1. Surface通過dequeueBuffer流程(具體操作在此不多贅述)獲取一塊存放繪制數(shù)據(jù)的buffer健霹。

  2. View 在onDraw的時(shí)候旺上,通過傳入的Canvas進(jìn)行繪制。(這里只是一個(gè)繪制的入口而已糖埋,本文是針對(duì)requestLayout流程來講述的宣吱,當(dāng)然你單獨(dú)用Canvas實(shí)現(xiàn)繪制也是一樣的)。

  3. 調(diào)用java層的CanvasAPI瞳别,實(shí)際真正負(fù)責(zé)繪制工作的是底層的Skia引擎征候,這里核心類包括SKCanvas(畫家)以及SKBitmap(畫布),繪制好的內(nèi)容放入Surface 通過dequeueBuffer獲取到的GraphicBuffer祟敛。

  4. 繪制完畢后疤坝,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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載跑揉,如需轉(zhuǎn)載請(qǐng)通過簡信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末埠巨,一起剝皮案震驚了整個(gè)濱河市历谍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辣垒,老刑警劉巖望侈,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異勋桶,居然都是意外死亡脱衙,警方通過查閱死者的電腦和手機(jī)侥猬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岂丘,“玉大人陵究,你說我怎么就攤上這事眠饮“铝保” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵仪召,是天一觀的道長寨蹋。 經(jīng)常有香客問我,道長扔茅,這世上最難降的妖魔是什么已旧? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮召娜,結(jié)果婚禮上运褪,老公的妹妹穿的比我還像新娘。我一直安慰自己玖瘸,他們只是感情好秸讹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著雅倒,像睡著了一般璃诀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔑匣,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天劣欢,我揣著相機(jī)與錄音,去河邊找鬼裁良。 笑死凿将,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的价脾。 我是一名探鬼主播丸相,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼彼棍!你這毒婦竟也來了灭忠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤座硕,失蹤者是張志新(化名)和其女友劉穎弛作,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體华匾,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡映琳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年机隙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萨西。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡有鹿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谎脯,到底是詐尸還是另有隱情葱跋,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布源梭,位于F島的核電站娱俺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏废麻。R本人自食惡果不足惜荠卷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烛愧。 院中可真熱鬧油宜,春花似錦、人聲如沸怜姿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽社牲。三九已至粪薛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搏恤,已是汗流浹背违寿。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留熟空,地道東北人藤巢。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像息罗,于是被迫代替她去往敵國和親掂咒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 故事源于身邊的人 用文字記錄下點(diǎn)滴 詞雖不算優(yōu)美華麗 但字字表達(dá)了真情 今天是2017年1月28日迈喉,大年初一绍刮,好多...
    有棱角的石頭閱讀 175評(píng)論 2 0
  • 勿言男兒少掛牽 誰曉愁海多綿纏 拄了竹杖 披了麻裳 悠悠地隨性徜徉 邀了明月 對(duì)影成雙 冥冥中闌干千行 嘆宦海無幄...
    蘭之猗閱讀 351評(píng)論 3 6
  • 我覺得代理可以出彩的有兩種人,這兩種人挨摸,都是可以做出相當(dāng)不錯(cuò)成績的人孩革,而往往那些夾在兩種類型之間的代理,反倒...
    熙熙Breathe閱讀 346評(píng)論 0 2
  • A:一起爬山的人太少得运,沒意思膝蜈? B:一人一世界锅移,得一人得世界。 欣賞一人饱搏,便欣賞全世界非剃。 A:不熱鬧,無聊推沸? B:...
    君遠(yuǎn)近閱讀 104評(píng)論 0 0