Android 12(S) 影像顯示系統 - GraphicBuffer同步機制 - Fence

二的次方發表於2022-05-20

必讀:

Android 12(S) 影像顯示系統 - 開篇


一、前言

前面的文章中講解Android BufferQueue的機制時,有遇到過Fence,但沒有具體講解。這篇文章,就針對Fence這種同步機制,做一些介紹。

Fence在Android影像顯示系統中用於GraphicBuffer的同步。我們不禁有疑問:那它和其它的同步機制相比有什麼特點呢?

Fence主要被用來處理跨硬體的情況,在我們關注的Android影像顯示系統中即處理CPU,GPU和HWC之間的資料同步。

 

二、Fence的基本作用

在Android BufferQueue的機制中,GraphicBuffer在生產者--圖形緩衝佇列--消費者之間流轉。每一個GraphicBuffer都有一個對應的BufferState標記其狀態,詳細可以參考:Android 12(S) 影像顯示系統 - BufferQueue的工作流程(八)

Android 12(S) 影像顯示系統 - GraphicBuffer同步機制 - Fence

本文作者@二的次方  2022-05-20釋出於部落格園

通常這裡的BufferState代表的僅僅是GraphicBuffer的所有權,即此刻它歸誰所擁有;但這塊快取是否可以被所有者使用還需要等待同步訊號到來,即使用權要等到相關的Fence訊號發出才可獲得。

在BufferState的各種狀態的定義的解釋中可以看到:

[/frameworks/native/libs/gui/include/gui/BufferSlot.h]
// DEQUEUED indicates that the buffer has been dequeued by the producer, but
// has not yet been queued or canceled. The producer may modify the
// buffer's contents as soon as the associated release fence is signaled.
// The slot is "owned" by the producer. 

// QUEUED indicates that the buffer has been filled by the producer and
// queued for use by the consumer. The buffer contents may continue to be
// modified for a finite time, so the contents must not be accessed until
// the associated fence is signaled. The slot is "owned" by BufferQueue.

// ACQUIRED indicates that the buffer has been acquired by the consumer. As
// with QUEUED, the contents must not be accessed by the consumer until the
// acquire fence is signaled. The slot is "owned" by the consumer. 

? DEQUEUED狀態時,雖然GraphicBuffer被生產者擁有,但直到和這塊快取相關的release fence同步訊號發出後,生產者才能修改GraphicBuffer的內容;

? QUEUED狀態時,雖然GraphicBuffer已經入佇列被BufferQueue持有,但直到和這塊快取相關的fence同步訊號發出前,生產者仍可能修改GraphicBuffer的內容,也即fence同步訊號發出後,才能去訪問其內容;

? ACQUIRED狀態時,雖然GraphicBuffer被生產者擁有,但是直到acquire fence同步訊號發出後,消費者才可去消費快取中的資料;

 


為什麼要把所有權和使用權分開呢?

主要還是CPU、GPU這種跨硬體程式設計同步處理的需要。GPU程式設計和純CPU程式設計有一個很大的不同就是它是非同步的,也就是說當我們呼叫GL command返回時這條命令並不一定真正執行完成了,只是把這個命令放在本地的command buffer裡。具體什麼時候這條GL command被真正執行完畢CPU是不知道的,除非CPU使用glFinish()等待這些命令執行完,另外一種方法就是基於同步物件的Fence機制。

 

而和GraphicBuffer相對應的BufferState狀態一定程度上說明了該GraphicBuffer的歸屬,但只指示了CPU裡的狀態,而GraphicBuffer的真正使用者一般是GPU。比如當生產者把一個GraphicBuffer放入BufferQueue時,只是在CPU層面完成了歸屬的轉移。但GPU說不定還在用,如果還在用的話消費者是不能拿去合成的。這時候GraphicBuffer和生產消費者的關係就比較曖昧了,消費者對GraphicBuffer具有擁有權,但無使用權,它需要等一個訊號,告訴它GPU用完了,消費者才真正擁有使用權。


一個簡化的模型如下:

GraphicBuffer由一個使用者傳遞到下一個使用者,並通過Fence訊號告知新的使用者什麼時候可以開始使用它。Fence的存在非常單純,從誕生開始就是為了在合適的時間發出一個訊號。

另一個角度來說,為什麼不在生產者把GraphicBuffer交給消費者時就呼叫glFinish()等GPU完成呢?這樣擁有權和使用權就一併傳遞了,無需Fence。就功能上這樣做是可以的,但效能會有影響,因為glFinish()是阻塞的,這時CPU為了等GPU自己也不能工作了。如果用Fence的話就可以等這個GraphicBuffer真正要被消費者用到時再阻塞,而那之前CPU和GPU是可以並行工作的。這樣相當於實現了臨界資源的lazy passing。

本文作者@二的次方  2022-05-20釋出於部落格園

 

三、簡單介紹Fence的實現

Fence,顧名思義就是把先到的攔住,等後來的,兩者步調一致了再往前走。抽象地說,Fence包含了同一或不同時間軸上的多個時間點,只有當這些點同時到達時Fence才會被觸發。

更詳細的介紹可以參考這篇文章Android Synchronization Fences – An Introduction

Fence可以由硬體實現(Graphic driver),也可以由軟體實現(Android kernel中的sw_sync)。EGL中提供了同步物件的擴充套件KHR_fence_sync。其中提供了eglCreateSyncKHR (),eglDestroySyncKHR()產生和銷燬同步物件。這個同步物件是往GL command佇列中插入的一個特殊操作,當執行到它時,會發出訊號指示佇列前面的命令已全部執行完畢。函式eglClientWaitSyncKHR()可讓呼叫者阻塞等待訊號發生。

在此基礎之上,Android對其進行了擴充套件 --- ANDROID_native_fence_sync ,新加了介面eglDupNativeFenceFDANDROID()。它可以把一個同步物件轉化為一個檔案描述符(反過來,eglCreateSyncKHR()可以把檔案描述符轉成同步物件)。這個擴充套件相當於讓CPU中有了GPU中同步物件的控制程式碼,檔案描述符可以在程式間傳遞(通過Binder等IPC機制),這就為多程式間的同步提供了基礎。我們知道Unix系統一切皆檔案,因此,有個這個擴充套件以後Fence的通用性大大增強了。

Android還進一步豐富了Fence的software stack。主要分佈在三部分:

  • C++ Fence類位於/frameworks/native/libs/ui/Fence.cpp;
  • C的libsync庫位於/system/core/libsync/sync.c;
  • Kernel driver部分

總得來說,kernel driver部分是同步的主要實現,libsync是對driver介面的封裝,Fence是對libsync的進一步的C++封裝。Fence會被作為GraphicBuffer的附屬隨著GraphicBuffer在生產者和消費間傳輸。另外Fence的軟體實現位於/drivers/base/sw_sync.c。SyncFeatures用以查詢系統支援的同步機制:/frameworks/native/libs/gui/SyncFeatures.cpp。

 

四、Fence在Android影像顯示系統的具體用法

Fence的主要作用就是保證GraphicBuffer在App, GPU和HWC三者間流轉時的資料讀寫同步(不同程式 or 不同硬體間的同步)。

概述下從APP渲染影像寫入GraphicBuffer經SurfaceFlinger處理最終呈現到Display的旅程:

1. GraphicBuffer先由App端作為生產者進行繪製,然後放入到BufferQueue,等待消費者取出作下一步的渲染合成;

2. SurfaceFlinger是Layer的管理者也是GraphicBuffer的消費者,畫面更新時,SurfaceFlinger會收集所有可見的圖層並詢問HWC各個圖層採用的合成方式;

3. 對於採用Device合成方式的圖層,SurfaceFlinger會直接把該圖層對應的GraphicBuffer的buffer handle通過呼叫setLayerBuffer放入HWC的Layer list;

4. 對於採用Client合成方式的圖層,即需要GPU繪製的層(超出HWC處理層數或者有複雜變換的),SurfaceFlinger會將所有待合成的圖層通過renderengine繪製到一塊client target buffer中,然後通過呼叫setClientTarget傳遞給HWC,此時SF對於APP來說是消費者,它消費APP生成的圖層資料,對於HWC來說SF是生產者,它生產target buffer給HWC消費;

參考:Android 12(S) 影像顯示系統 - SurfaceFlinger GPU合成/CLIENT合成方式 - 隨筆1

5. HWC最後疊加所有圖層再往Display呈現出來,這時HWC是消費者。整個大致流程如圖:


[/hardware/interfaces/graphics/composer/2.1/IComposerClient.hal]
/** Possible composition types for a given layer. */
    enum Composition : int32_t {
        INVALID = 0,
        CLIENT = 1, 
        DEVICE = 2,
        SOLID_COLOR = 3,
        CURSOR = 4,
        SIDEBAND = 5,
    };

可以看到,對於合成方式是CLIENT的圖層來說,其GraphicBuffer先後經過兩個生產消費者模型:

⚽ 應用渲染時 == 生產者/消費者模型

⚽ SurfaceFlinger的GPU合成時 == 生產者/消費者模型 

我們知道GraphicBuffer核心包含的是buffer_handle_t結構,它指向的native_handle_t包含了Gralloc中申請出來的圖形緩衝區的檔案描述符和其它基本屬性,這個檔案描述符會被同時對映到客戶端和服務端,作為共享記憶體(跨程式的共享)。

 

由於服務端程式和客戶端程式都可以訪問同一實體記憶體,因此不加同步的話會引起錯誤(讀寫衝突/混亂)。為了協調客戶端和服務端不同程式下對同一共享記憶體的訪問,在傳輸GraphicBuffer時,還帶有Fence,標誌了它是否被上一個使用者使用完畢。

Fence按作用大體分兩種:acquireFence和releaseFence。前者用於生產者通知消費者生產已完成,後者用於消費者通知生產者消費已完成。下面分別看一下這兩種Fence的產生和使用過程。首先是acquireFence的使用流程:

當App端通過queueBuffer()向BufferQueue插入GraphicBuffer時,會順帶一個Fence,這個Fence指示這個GraphicBuffer是否已被生產者用好。之後該GraphicBuffer被消費者通過acquireBuffer()拿走,同時也會取出這個acquireFence。之後消費者(也就是SurfaceFlinger)要把它拿來渲染時,需要等待Fence被觸發,之後才可以處理這塊快取中的資料。

我們看一下相關流程中的關鍵程式碼片段:

生產端在queueBuffer時,傳遞進來Fence

[ /frameworks/native/libs/gui/Surface.cpp ]
int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
    ... // fenceFd即為生產者傳遞進來的,進一步封裝為Fence Object,連同GraphicBuffer一塊入佇列
    getQueueBufferInputLocked(buffer, fenceFd, mTimestamp, &input);
    status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
    ...
}

消費端在acquireBuffer時,同時取得acquireFence,傳遞給最終的消費者SurfaceFlinger

[ /frameworks/native/libs/gui/BLASTBufferQueue.cpp ]
void BLASTBufferQueue::processNextBufferLocked(bool useNextTransaction) {
    ...
    BufferItem bufferItem;
    status_t status =
    mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false); //請求可用的buffer
    ...
    auto buffer = bufferItem.mGraphicBuffer;
    ...
    t->setBuffer(mSurfaceControl, buffer, releaseCallbackId, releaseBufferCallback); // 傳遞Buffer給SurfaceFlinger
    t->setDataspace(mSurfaceControl, static_cast<ui::Dataspace>(bufferItem.mDataSpace));
    t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata);
    t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage);
    t->setAcquireFence(mSurfaceControl, // 傳遞fence給SurfaceFlinger
                       bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE);
    ...
}

SurfaceFlinger接受buffer與fence並設定到對應的Layer

[/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp]
uint32_t SurfaceFlinger::setClientStateLocked() {
    ...
    if (what & layer_state_t::eAcquireFenceChanged) {
        if (layer->setAcquireFence(s.acquireFence)) flags |= eTraversalNeeded; // 傳遞給Layer
    }
    ...
        if (layer->setBuffer(buffer, s.acquireFence, postTime, desiredPresentTime, isAutoTimestamp, // 傳遞給Layer
                             s.cachedBuffer, frameNumber, dequeueBufferTimestamp, frameTimelineInfo,
                             s.releaseBufferListener)) {
            flags |= eTraversalNeeded;
        }
    ...
}

Layer把資訊 Buffer & acquireFence儲存到mDrawingState

mDrawingState.buffer = buffer;
mDrawingState.acquireFence = fence;

合成階段等待acquireFence觸發後再去處理資料

SurfaceFlinger在對Layer進行重新繪製時,BufferLayer::latchBuffer中會先去判斷mDrawingState記錄的最新設定下來的GraphicBuffer的acquire fence是否已經signaled,沒有的話會再繼續SurfaceFlinger::signalLayerUpdate ==> invalidate嘗試,直到acquire fence被觸發,再去繼續執行updateTexImage==>updateActiveBuffer等合成前的準備工作。

// invalidate
SurfaceFlinger::handleMessageInvalidate()
// pageflip
SurfaceFlinger::handlePageFlip()
// 然後呼叫到latchBuffer
BufferLayer::latchBuffer() {
    ...
    // If the head buffer's acquire fence hasn't signaled yet, return and
    // try again later
    if (!fenceHasSignaled()) { // 等待acquire fence的邏輯
        ATRACE_NAME("!fenceHasSignaled()");
        mFlinger->signalLayerUpdate(); // 會再次觸發invalidate
        return false;
    }
    ...
}

 

注:上面的理解,我也只是根據程式碼來判斷的,沒有實際驗證過,理解也許有錯,僅供參考!

本文作者@二的次方  2022-05-20釋出於部落格園

如果圖層是DEVICE合成方式渲染,那麼不需要經過GPU,那就需要把這些層對應的acquireFence傳到HWC中。這樣,HWC在合成前就能確認這個buffer是否已被生產者使用完,因此一個正常點的HWC需要等這些個acquireFence全被觸發才能去繪製。所以在向HWC設定setLayerBuffer時,也會把傳遞acquireFence

[/frameworks/native/services/surfaceflinger/DisplayHardware/ComposerHal.cpp]
Error Composer::setLayerBuffer(Display display, Layer layer,
        uint32_t slot, const sp<GraphicBuffer>& buffer, int acquireFence)
{
    mWriter.selectDisplay(display);
    mWriter.selectLayer(layer);

    const native_handle_t* handle = nullptr;
    if (buffer.get()) {
        handle = buffer->getNativeBuffer()->handle;
    }

    mWriter.setLayerBuffer(slot, handle, acquireFence);
    return Error::NONE;
}

 

我這裡有一個疑問:如果上面BufferLayer::latchBuffer中分析的等待acquireFence的邏輯是正確的,那這個等待的邏輯看起來是直接傳遞給HWC合成的buffer也會經過這裡,那是不是setLayerBuffer再傳遞acquireFence給HWC,其實HWC就無需再判斷acquireFence是否被觸發了呢?因為latchBuffer中的邏輯保證了傳遞給HWC的buffer肯定是可以直接操作的了

 

對於CLIENT合成方式的圖層,因為HWC不需要直接這些層同步,它只要和這些CLIENT圖層合成的結果FramebufferTarget同步就可以了。

GPU程式合成CLIENT圖層前,會去RenderSurface::dequeueBuffer獲取一塊GraphicBuffer用來儲存合成的結果,然後使用SkiaGLRenderEngine::drawLayers去把所有CLIENT圖層畫到這塊圖形快取中。

[/frameworks/native/libs/renderengine/skia/SkiaGLRenderEngine.cpp]
status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
                                        const std::vector<const LayerSettings*>& layers,
                                        const std::shared_ptr<ExternalTexture>& buffer,
                                        const bool /*useFramebufferCache*/,
                                        base::unique_fd&& bufferFence, base::unique_fd* drawFence) {
    ...
    // wait on the buffer to be ready to use prior to using it
    waitFence(bufferFence); // 等待buffer可用,這塊buffer就是通過RenderSurface::dequeueBuffer獲取的
    ...
    if (drawFence != nullptr) {
        *drawFence = flush(); // 這個drawFence用作同步HWC,告訴HWC: 我GPU畫完了,你HWC消費吧
    }
}

GPU合成完CLIENT圖層後,通過RenderSurface::queueBuffer()將GraphicBuffer放入對應的BufferQueue,包括對應的Fence == SkiaGLRenderEngine::drawLayers時產生的;然後消費端FramebufferSurface通過advanceFrame() --> nextBuffer() -> acquireBufferLocked()從BufferQueue中拿一個GraphicBuffer,附帶拿到它的acquireFence。接著呼叫

[/frameworks/native/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp]
status_t FramebufferSurface::nextBuffer(uint32_t& outSlot,
        sp<GraphicBuffer>& outBuffer, sp<Fence>& outFence,
        Dataspace& outDataspace) {
    ...
    status_t result = mHwc.setClientTarget(mDisplayId, outSlot, outFence, outBuffer, outDataspace);
    ...
}

其中會把剛才acquire的GraphicBuffer連帶acquireFence通過呼叫setClientTarget設到HWC的ClientTarget Layer。

 

綜上,HWC進行最後合成處理的前提是CLIENT圖層層的acquireFence及FramebufferTarget的acquireFence都被觸發。

 

看完acquireFence,再看看releaseFence的使用流程:

 

合成過程中,先是GPU工作,在Output::composeSurfaces()函式中對CLIENT合成方式的圖層進行合成,結果放在framebuffer中(即RenderSurface::dequeueBuffer獲得的GraphicBuffer)。然後SurfaceFlinger會呼叫Output::postFramebuffer()讓HWC開始工作。postFramebuffer()中最主要是呼叫Display::presentAndGetFrameFences()最終會呼叫到HWC的方法presentgetReleaseFences通知HWC合成顯示並返回得到releaseFence。

HWC中產生的releaseFence主要是:同步DEVICE合成方式的Layer的GraphicBuffer的釋放;

[/frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp]
void Output::postFramebuffer() {
    ...
    auto& outputState = editState();
    outputState.dirtyRegion.clear();
    mRenderSurface->flip();

    auto frame = presentAndGetFrameFences(); // 通知HWC合成顯示並獲取到releaseFence

    mRenderSurface->onPresentDisplayCompleted(); // 通知FramebufferSurface進行release buffer ==>走到FramebufferSurface::onFrameCommitted()

    for (auto* layer : getOutputLayersOrderedByZ()) {
        // The layer buffer from the previous frame (if any) is released
        // by HWC only when the release fence from this frame (if any) is
        // signaled.  Always get the release fence from HWC first.
        sp<Fence> releaseFence = Fence::NO_FENCE;

        if (auto hwcLayer = layer->getHwcLayer()) { // 獲取Layer對應的releaseFence
            if (auto f = frame.layerFences.find(hwcLayer); f != frame.layerFences.end()) {
                releaseFence = f->second;
            }
        }

        // If the layer was client composited in the previous frame, we
        // need to merge with the previous client target acquire fence.
        // Since we do not track that, always merge with the current
        // client target acquire fence when it is available, even though
        // this is suboptimal.
        // TODO(b/121291683): Track previous frame client target acquire fence.
        if (outputState.usesClientComposition) {
            // 如果有GPU合成Layer(Client合成方式),做Fence::merge
            // releaseFence -- HWC生成的
            // clientTargetAcquireFence -- 本質是 RenderSurface::queueBuffer傳入的 acquire fence,是在SkiaGLRenderEngine::drawLayers中生成的
            releaseFence =
                    Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence);
        }

        layer->getLayerFE().onLayerDisplayed(releaseFence); // 把releaseFence同步到各個Layer
    }
    ...
}

 

對於DEVICE合成的Layer,因為是HWC消費這些圖層的GraphicBuffer資料。所以HWC產生的release fence來同步這些圖形快取的釋放;

對於CLIENT合成的Layer,因為是GPU消費這些圖層的GraphicBuffer資料併合成到client target中。所以當GPU完成合成時就意味著這些圖層的快取資料就不再被使用了,因此client target acquire fence作為release fence來同步這些圖形快取的釋放;

client target acquire fence即表示GPU合成完了,HWC可以消費了,也意味著相關Layer的影像快取可以釋放了。

 

release fence需要同步到Layer。實現位於Layer的onLayerDisplayed()函式中:

[/frameworks/native/services/surfaceflinger/BufferStateLayer.cpp]
void BufferStateLayer::onLayerDisplayed(const sp<Fence>& releaseFence) {
    ...
    sp<CallbackHandle> ch;
    for (auto& handle : mDrawingState.callbackHandles) {
        if (handle->releasePreviousBuffer) {
            ch = handle;
            break;
        }
    }
    auto status = addReleaseFence(ch, releaseFence);
    if (status != OK) {
        ALOGE("Failed to add release fence for layer %s", getName().c_str());
    }

    mPreviousReleaseFence = releaseFence;
}

 

中間還會有一些過程,然後經過Binder 回撥,會通知到BLASTBufferQueue,我們在setBuffer時,有設定一個releaseBufferCallback,這個回撥中會收到release fence,最終會調到 releaseBuffer 連同fence一塊歸還到快取佇列中,之後生產者就可以dequeueBuffer時取到這塊快取了

[/frameworks/native/libs/gui/BLASTBufferQueue.cpp]
void BLASTBufferQueue::processNextBufferLocked(bool useNextTransaction) {

    auto releaseBufferCallback =
            std::bind(releaseBufferCallbackThunk, wp<BLASTBufferQueue>(this) /* callbackContext */,
                      std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
                      std::placeholders::_4);
    t->setBuffer(mSurfaceControl, buffer, releaseCallbackId, releaseBufferCallback); // 設定了releaseBufferCallback
    
}

 


那對於儲存GPU繪製結果的那一層呢?

Output::postFramebuffer() ==> RenderSurface::onPresentDisplayCompleted() ==>FramebufferSurface::onFrameCommitted()

[/frameworks/native/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp]
void FramebufferSurface::onFrameCommitted() {
    if (mHasPendingRelease) {
        sp<Fence> fence = mHwc.getPresentFence(mDisplayId); // 獲取release fence
        if (fence->isValid()) {
            status_t result = addReleaseFence(mPreviousBufferSlot,
                    mPreviousBuffer, fence);
            ALOGE_IF(result != NO_ERROR, "onFrameCommitted: failed to add the"
                    " fence: %s (%d)", strerror(-result), result);
        }
        status_t result = releaseBufferLocked(mPreviousBufferSlot, mPreviousBuffer); // release buffer
        ALOGE_IF(result != NO_ERROR, "onFrameCommitted: error releasing buffer:"
                " %s (%d)", strerror(-result), result);

        mPreviousBuffer.clear();
        mHasPendingRelease = false;
    }
}


此處拿到HWC生成的FramebufferTarget的releaseFence,設到FramebufferSurface中相應的GraphicBuffer Slot中。這樣FramebufferSurface對應的GraphicBuffer也可以被釋放回BufferQueue了。當將來EGL從中拿到這個buffer時,照例也要先等待這個releaseFence觸發才能使用。

 

講到這裡,fence的內容就差不多了。不過好多細節的東西都沒分析,也許還有理解錯誤。

本文作者@二的次方  2022-05-20釋出於部落格園


Fence在圖形顯示系統中,我覺得簡單點講,應該就概括為2個作用:

1. 生產者填充資料到GraphicBuffer,並附帶產生一個acquire fence,這塊buffer連同fence一塊返回到BufferQueue,acquireBuffer後被消費者取走  --> 此時acquire fence的作用

// 消費者焦急的等待....

生產者:Hi, 消費者,我把資料都寫到buffer了,你準備盡情消費吧!
消費者:收到,那我開始消費了哦!

2. 消費者使用完GraphicBuffer中的資料,並附帶產生一個release fence,這塊buffer連同fence一塊返回到BufferQueue,dequeueBuffer後被生產者取走  --> 此時release fence的作用

// 生產者焦急的等待....

消費者:Hi,生產者,這塊buffer中的資料我消費完了,不再使用了哦!
生產者:收到,那我就向這塊buffer中填充新的資料了

  也許理解不正確,謹慎閱讀,就先這樣吧?


 

 

----

本文參考了很多有價值的文章,並據此修改來理解Android 12 中的邏輯,站在巨人的肩膀上可以看的更遠。本篇僅供學習參考,理解上難免有錯誤!

https://blog.csdn.net/jinzhuojun/article/details/39698317 

https://zhuanlan.zhihu.com/p/68782630

https://www.jianshu.com/p/3c61375cc15b

 

相關文章