Android硬件加速過程分析

繪制基礎(chǔ)

安卓繪制框架

繪制框架.png

應(yīng)用層
1.通過與WindowManagerService通信,獲取一塊surface。
2.通過View系統(tǒng)或OpenGL、Skia在這塊surface上繪制。
3.與WindowManagerService通信,提交繪制產(chǎn)生的緩存。
服務(wù)層(WindowManagerService)
1.生成、釋放surface
2.管理surface的層級
3.獲取surface的數(shù)據(jù),將surface的數(shù)據(jù)進(jìn)行混合,生成新的一幀。
4.將新的一幀數(shù)據(jù)寫入內(nèi)核,使得顯示器刷新數(shù)據(jù)
關(guān)鍵類:
1.Canvas:畫布,里面有一個Bitmap,用于保存繪制過程中的緩存。此類提供了繪制的基本方法,包括作圓、線等。
2.Paint:畫筆,提供繪制的基本參數(shù),比如說顏色,畫筆的粗細(xì)等。
3.Surface:可以獲取畫布并提交繪制結(jié)果。本質(zhì)上是一個代理類。用于Application與WindowManager通信。
4.ViewRootImp:實現(xiàn)ViewParent,包含Surface和Choreographer。用于控制Activity的繪制。

  1. Choreographer繪制調(diào)度器。用于控制各個Surface的刷新步調(diào),讓所以Surface基本上同時開始刷新。一般16ms觸發(fā)一次刷新。
  2. SurfaceFlinger:混合各個Surface的數(shù)據(jù),形成新的一幀數(shù)據(jù)。

View的重繪流程

以最基本的View的invalidate為例
基本繪制流程


繪制新流程.png

流程圖中有小小錯誤,能找到嗎?
基本流程可以分成兩塊:
一、觸發(fā)刷新
子view調(diào)用invalidate,然后層層遞進(jìn),調(diào)到ViewrootImp的ScheduleTraversals,在這個過程中不斷確定重繪的區(qū)域,更新各個父控件的繪制標(biāo)志。完成各個層級父控制的繪制標(biāo)志的刷新之后,利用ViewrootImp中的Choreographer控制刷新的時機(jī),觸發(fā)刷新。
二、刷新過程

  • 首先通過Surface的lockCanvas申請一塊新的畫布,獲取一個Canvas。
    值得注意的是,WindowManagerService會為每一塊surface分配兩塊內(nèi)存,
    一塊作為顯示使用(讀),一塊作為繪制使用(寫)。在完成繪制后,兩塊內(nèi)存的作用互換。lockCanvas的作用,實際上時是給待繪制的內(nèi)存上鎖,并且獲取待繪制內(nèi)存的地址,把這塊內(nèi)存作為畫布的緩存。在lockCanvas時,會將顯示的內(nèi)存的數(shù)據(jù)拷貝到待繪制的內(nèi)存中,同時傳入一個區(qū)域(dirty),作為重繪的區(qū)域。每次重繪實際只需要繪制需要修改的區(qū)域(dirty區(qū)域),其它區(qū)域保持不變,這樣,就大大加快了繪制效率

    繪制內(nèi)存切換.png

  • 獲取畫布之后,就需要層層調(diào)用View的draw方法,在這塊畫布上在做畫。
    每一個View都有一塊緩存mDrawCache。View的繪制過程,首先要通過繪制標(biāo)志,判斷View的內(nèi)容是否發(fā)生變化,有發(fā)生變化,則調(diào)用ondraw方法,重新獲取mDrawCache。最后將mDrawCache的重繪區(qū)域繪制到畫布中。
    ViewGroup包含自己本身的繪制和子View的繪制。自己本身的繪制和View一樣,子View的繪制需要層層調(diào)用子View的繪制方法。

    draw過程.png

  • 完成繪制后,調(diào)用unlockCanvasAndPost接口。將繪制內(nèi)容提交給WindowManagerService。
    接口unlockCanvasAndPost的實質(zhì)是將繪制的內(nèi)存解鎖,并且通知WindowManagerService將兩塊內(nèi)存的作用互換,這樣,SurfaceFlinger就會將最新繪制后得到的內(nèi)存進(jìn)行混合顯示。

硬件加速

為什么使用硬件加速

CPU和GPU架構(gòu)

CPU : Central Processing Unit , 中央處理器,是計算機(jī)設(shè)備核心器件,用于執(zhí)行程序代碼。
GPU : Graphic Processing Unit , 圖形處理器,主要用于處理圖形運算,通常所說“顯卡”的核心部件就是GPU。
下面是CPU和GPU的結(jié)構(gòu)對比圖。其中:

  • 黃色的Control為控制器,用于協(xié)調(diào)控制整個CPU的運行,包括取出指令、控制其他模塊的運行等;

  • 綠色的ALU(Arithmetic Logic Unit)是算術(shù)邏輯單元,用于進(jìn)行數(shù)學(xué)、邏輯運算;
    橙色的Cache和DRAM分別為緩存和RAM,用于存儲信息。


    cpu和gpu架構(gòu)圖.png
  • 從結(jié)構(gòu)圖可以看出,CPU的控制器較為復(fù)雜,而ALU數(shù)量較少。因此CPU擅長各種復(fù)雜的邏輯運算,但不擅長數(shù)學(xué)尤其是浮點運算。

  • 和CPU不同的是,GPU就是為實現(xiàn)大量數(shù)學(xué)運算設(shè)計的。從結(jié)構(gòu)圖中可以看到,GPU的控制器比較簡單,但包含了大量ALU。GPU中的ALU使用了并行設(shè)計,且具有較多浮點運算單元。

硬件加速的主要原理,就是通過底層軟件代碼,將CPU不擅長的圖形計算轉(zhuǎn)換成GPU專用指令,由GPU完成。

硬件加速的開啟

Android 系統(tǒng)的 UI 從 繪制 到 顯示在屏幕上 是分兩個步驟的
第一步:在Android 應(yīng)用程序這一側(cè)進(jìn)行的。(將 UI 構(gòu)建到一個圖形緩沖區(qū) Buffer 中,交給SurfaceFlinger )
第二步:在SurfaceFlinger進(jìn)程這一側(cè)進(jìn)行的。(獲取Buffer 并合成以及顯示到屏幕中。)
其中,第二步在 SurfaceFlinger 的操作一直是以硬件加速方式完成的,所以我們說的硬件加速一般指的是在 應(yīng)用程序 圖形通過GPU加速渲染 到 Buffer 的過程。

在Android中,可以四給不同層次上開啟硬件加速:

  • 應(yīng)用:
    <application android:hardwareAccelerated="true">
  • Activity
    <activity android:hardwareAccelerated="true">
  • Window
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
  • View
    view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    在這四個層次中,應(yīng)用和Activity是可以選擇的,Window只能打開,View只能關(guān)閉。

硬件加速的流程分析

模式模型

構(gòu)建階段

所謂構(gòu)建就是遞歸遍歷所有視圖,將需要的操作緩存下來,之后再交給單獨的Render線程利用OpenGL渲染。在Android硬件加速框架中,View視圖被抽象成RenderNode節(jié)點。


構(gòu)建Rendor緩存.png

View中的繪制都會被抽象成一個個DrawOp(DisplayListOp),比如View中drawLine,構(gòu)建中就會被抽象成一個DrawLintOp,drawBitmap操作會被抽象成DrawBitmapOp,每個子View的繪制被抽象成DrawRenderNodeOp,每個DrawOp有對應(yīng)的OpenGL繪制命令,同時內(nèi)部也握著繪圖所需要的數(shù)據(jù)。

DrawOp.png

如此以來,每個View不僅僅握有自己DrawOp List,同時還拿著子View的繪制入口,如此遞歸,便能夠統(tǒng)計到所有的繪制Op,很多分析都稱為Display List,源碼中也是這么來命名類的,不過這里其實更像是一個樹,而不僅僅是List,示意如下:


displayList結(jié)構(gòu)圖-2.png

構(gòu)建完成后,就可以將這個繪圖Op樹交給Render線程進(jìn)行繪制,這里是同軟件繪制很不同的地方,軟件繪制時,View一般都在主線程中完成繪制,而硬件加速,除非特殊要求,一般都是在單獨線程中完成繪制,如此以來就分擔(dān)了主線程很多壓力,提高了UI線程的響應(yīng)速度。


CPU和GPU分工合作圖.jpg

構(gòu)建過程源碼分析

構(gòu)建階段流程圖.png

從流程圖可以看出,HardwareRenderer是整個硬件加速繪制的入口。整個繪制的過程通過不斷dispatchGetDisplayList或者dispathDraw遍歷View,刷新displayList。
從名字能看出,ThreadedRenderer應(yīng)該跟一個Render線程息息相關(guān),不過ThreadedRenderer是在UI線程中創(chuàng)建的,那么與UI線程也必定相關(guān),其主要作用:

1、在UI線程中完成DrawOp集構(gòu)建
2、負(fù)責(zé)跟渲染線程通信

可見ThreadRenderer的作用是很重要的,簡單看一下實現(xiàn):

ThreadedRenderer(Context context, boolean translucent) {
    ...
    <!--新建native node-->
    long rootNodePtr = nCreateRootRenderNode();
    mRootNode = RenderNode.adopt(rootNodePtr);
    mRootNode.setClipToBounds(false);
    <!--新建NativeProxy-->
    mNativeProxy = nCreateProxy(translucent, rootNodePtr);
    ProcessInitializer.sInstance.init(context, mNativeProxy);
    loadSystemProperties();
}
  • RootNode用來標(biāo)識整個DrawOp樹的根節(jié)點,有個這個根節(jié)點就可以訪問所有的繪制Op.
  • RenderProxy對象,這個對象就是用來跟渲染線程進(jìn)行通信的句柄
    再看一下RenderProxy的源碼
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
        : mRenderThread(RenderThread::getInstance())
        , mContext(nullptr) {
    SETUP_TASK(createContext);
    args->translucent = translucent;
    args->rootRenderNode = rootRenderNode;
    args->thread = &mRenderThread;
    args->contextFactory = contextFactory;
    mContext = (CanvasContext*) postAndWait(task);
    mDrawFrameTask.setContext(&mRenderThread, mContext);  
   }
  • RenderThread是一個單例線程,也就是說,每個進(jìn)程最多只有一個硬件渲染線程,這樣就不會存在多線程并發(fā)訪問沖突問題,到這里其實環(huán)境硬件渲染環(huán)境已經(jīng)搭建好好了。
    ThreadRender的繪制入口
@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
    attachInfo.mIgnoreDirtyState = true;

    final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
    choreographer.mFrameInfo.markDrawStart();
    <!--關(guān)鍵點1:構(gòu)建View的DrawOp樹-->
    updateRootDisplayList(view, callbacks);

    <!--關(guān)鍵點2:通知RenderThread線程繪制-->
    int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
    ...
}
  • updateRootDisplayList,構(gòu)建RootDisplayList,其實就是構(gòu)建View的DrawOp樹,updateRootDisplayList會進(jìn)而調(diào)用根View的updateDisplayListIfDirty,讓其遞歸子View的updateDisplayListIfDirty,從而完成DrawOp樹的創(chuàng)建
  • 通知RenderThread線程繪制
    構(gòu)建樹的流程
private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
    <!--更新-->
    updateViewTreeDisplayList(view);
   if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
      <!--獲取DisplayListCanvas-->
        DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
        try {
        <!--利用canvas緩存Op-->
            final int saveCount = canvas.save();
            canvas.translate(mInsetLeft, mInsetTop);
            callbacks.onHardwarePreDraw(canvas);

            canvas.insertReorderBarrier();
            canvas.drawRenderNode(view.updateDisplayListIfDirty());
            canvas.insertInorderBarrier();

            callbacks.onHardwarePostDraw(canvas);
            canvas.restoreToCount(saveCount);
            mRootNodeNeedsUpdate = false;
        } finally {
        <!--將所有Op填充到RootRenderNode-->
            mRootNode.end(canvas);
        }
    }
}
  • 利用View的RenderNode獲取一個DisplayListCanvas
  • 利用DisplayListCanvas構(gòu)建并緩存所有的DrawOp
  • 將DisplayListCanvas緩存的DrawOp填充到RenderNode
  • 將根View的緩存DrawOp設(shè)置到RootRenderNode中,完成構(gòu)建
    DisplayList的更新過程
  // Don't need to recreate the display list, just need to tell our
            // children to restore/recreate theirs
            if (renderNode.isValid()
                    && !mRecreateDisplayList) {
                mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchGetDisplayList();

                return renderNode; // no work needed
            }

            // If we got here, we're recreating it. Mark it as such to ensure that
            // we copy in child display lists into ours in drawChild()
            mRecreateDisplayList = true;

            int width = mRight - mLeft;
            int height = mBottom - mTop;
            int layerType = getLayerType();

            final DisplayListCanvas canvas = renderNode.start(width, height);

            try {
                if (layerType == LAYER_TYPE_SOFTWARE) {
                    buildDrawingCache(true);
                    Bitmap cache = getDrawingCache(true);
                    if (cache != null) {
                        canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                    }
                } else {
                    computeScroll();

                    canvas.translate(-mScrollX, -mScrollY);
                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;

                    // Fast path for layouts with no backgrounds
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        dispatchDraw(canvas);
                        drawAutofilledHighlight(canvas);
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                        if (debugDraw()) {
                            debugDrawFocus(canvas);
                        }
                    } else {
                        draw(canvas);
                    }
                }
            } finally {
                renderNode.end(canvas);
                setDisplayListProperties(renderNode);
            }
  • 如果本View不需要更新RendorNode,則調(diào)用dispathcGetDisplayList,讓其子view更新RendorNode。最終會導(dǎo)致步驟2。
  • 如果本View需要更新RendorNode,則調(diào)用draw(Canvas)方法。
    繼續(xù)跟蹤draw(Canvas)方法
 if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

我們知道每個view的DrawRenderNodeOp緩存主要包括displayListOps和其子View的DrawRenderNodeOp

  • drawBackground、onDraw 、onDrawForeground、drawDefaultFocusHighlight是更新自身相關(guān)的displayListOps
  • dispatchDraw向下觸發(fā)便利,更新子View相關(guān)的DrawRenderNodeOp。


    drawOps更新.png

    dispatchDraw最終會走到draw(Canvas canvas, ViewGroup parent, long drawingTime) ,看一下里面的代碼

....
   if (drawingWithRenderNode) {
            // Delay getting the display list until animation-driven alpha values are
            // set up and possibly passed on to the view
            renderNode = updateDisplayListIfDirty();
            if (!renderNode.isValid()) {
                // Uncommon, but possible. If a view is removed from the hierarchy during the call
                // to getDisplayList(), the display list will be marked invalid and we should not
                // try to use it again.
                renderNode = null;
                drawingWithRenderNode = false;
            }
        }
 if (!drawingWithDrawingCache) {
            if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);
                }
            }
        } 
....
  • 子view通過updateDisplayListIfDirty刷新自己的RendNode。如果子View本身,以及子View的子控件沒有變化,RenderNode不會發(fā)生實質(zhì)的變化。
  • 最后通過DisplayListCanvas的drawRenderNode將繪制緩存保存到父View 中

DisplayListCanvas類分析,以drawLine為例

void DisplayListCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
    points = refBuffer<float>(points, count);

    addDrawOp(new (alloc()) DrawLinesOp(points, count, refPaint(&paint)));
}

RenderThread渲染過程

DrawOp樹構(gòu)建完畢后,UI線程利用RenderProxy向RenderThread線程發(fā)送一個DrawFrameTask任務(wù)請求,RenderThread被喚醒,開始渲染。

  • 首先進(jìn)行DrawOp的合并
  • 接著繪制特殊的Layer
  • 最后繪制其余所有的DrawOpList
  • 調(diào)用swapBuffers將前面已經(jīng)繪制好的圖形緩沖區(qū)提交給Surface Flinger合成和顯示。

開啟硬件加速的優(yōu)缺點

優(yōu)點

  • 硬件加速使用雙線程工作,主線程只負(fù)責(zé)構(gòu)建繪制樹,渲染線程負(fù)責(zé)渲染。主線程基本上不會因為繪制超時而卡頓。
  • GPU分擔(dān)了CPU的渲染任務(wù),CPU有多余的時間做其他重要的事情,這將使得手機(jī)整體表現(xiàn)將更加流暢。
  • GPU擅長做渲染工作,硬件加速允許應(yīng)用執(zhí)行繁重的渲染任務(wù)。如果沒有GPU或者不采用硬件加速,播放視頻將非常卡頓
    缺點
  • 硬件加速屬于雙緩沖機(jī)制,使用顯存進(jìn)行頁面渲染(使用較少的物理內(nèi)存),導(dǎo)致更頻繁的顯存操作,可能引起以下現(xiàn)象:
    花屏、閃屏
  • 硬件加速的準(zhǔn)備工作較長,可能在應(yīng)用剛啟動時,存在掉幀現(xiàn)象。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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