Android 自定義View之Draw過(guò)程(下)

前言

Draw 過(guò)程系列文章

上篇分析了硬件加速相關(guān)知識(shí):
Android 自定義View之Draw過(guò)程(中)
本篇將從代碼的角度深入分析硬件加速繪制與軟件繪制。
通過(guò)本篇文章,你將了解到:

1、軟件繪制流程
2、硬件加速繪制流程
2、LayerType 對(duì)繪制的影響
3、Canvas 從哪里來(lái)到哪里去
4、繪制流程全家福

軟件繪制流程

上篇說(shuō)過(guò)在ViewRootImpl->draw(xx)里軟件繪制與硬件加速繪制分道揚(yáng)鑣:


image.png

上圖是Window 區(qū)分硬件加速繪制與軟件繪制的入口。
由易到難,先來(lái)看看軟件繪制流程。

drawSoftware(xx)

#ViewRootImpl.java
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                                 boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
        //持有的畫布
        final Canvas canvas;
        ...
        try {
            ...
            //申請(qǐng)畫布對(duì)象,該畫布初始大小為dirty的尺寸
            canvas = mSurface.lockCanvas(dirty);
            //設(shè)置密度
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            ...
        } catch (IllegalArgumentException e) {
            ...
            return false;
        } finally {
            ...
        }

        try {
            //畫布是否需要移動(dòng)
            canvas.translate(-xoff, -yoff);
            //mView 即是添加到該Window的RootView
            //對(duì)于Activity、Dialog開(kāi)啟的Window,mView就是我們熟知的DecorView
            //rootView draw()方法
            mView.draw(canvas);
        } finally {
            try {
                //提交繪制的內(nèi)容到Surface
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                ...
            }
        }
        return true;
    }

以上方法功能重點(diǎn)如下:

1、從Surface 申請(qǐng)Canvas對(duì)象,該Canvas為CompatibleCanvas 類型
2、拿到Canvas后,調(diào)用View.draw(Canvas)開(kāi)始繪制RootView
3、整個(gè)ViewTree 繪制完成后將內(nèi)容提交到Surface

注:RootView 只是個(gè)代稱,并不是某個(gè)View的名字。
一些常見(jiàn)的RootView 請(qǐng)移步:Android 輸入事件一擼到底之源頭活水(1)

關(guān)于View.draw(xx)方法在:Android 自定義View之Draw過(guò)程(上) 已做過(guò)詳細(xì)分析,結(jié)合上述代碼,用如下圖表示:

image.png

可以看得出來(lái),軟件繪制有如下特點(diǎn):

  • 從RootView 遞歸調(diào)用子布局的draw(xx)方法,直到每個(gè)符合條件的View都進(jìn)行了繪制
  • 繪制過(guò)程中,所有的View持有相同的Canvas對(duì)象

引入問(wèn)題1:既然所有的View都持有相同的Canvas,那么每個(gè)View繪制的起點(diǎn)、終點(diǎn)是如何確定的呢?
該問(wèn)題稍后分析。

硬件加速繪制流程

概要

軟件繪制是將Canvas的一系列操作寫入到Bitmap里,而對(duì)于硬件加速繪制來(lái)說(shuō),每個(gè)View 都有一個(gè)RenderNode,當(dāng)需要繪制的時(shí)候,從RenderNode里獲取一個(gè)RecordingCanvas,與軟件繪制一樣,也是調(diào)用Canvas一系列的API,只不過(guò)調(diào)用的這些API記錄為一系列的操作行為存放在DisplayList。當(dāng)一個(gè)View錄制結(jié)束,再將DisplayList交給RenderNode。此時(shí),繪制的步驟已經(jīng)記錄在RenderNode里,到此,針對(duì)單個(gè)View的硬件繪制完成,這個(gè)過(guò)程也稱作為DisplayList的構(gòu)建過(guò)程。

調(diào)用過(guò)程分析

來(lái)看看硬件加速的入口:

#ThreadedRenderer.java
    void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
        ...
        //(1)--->錄制操作
        //更新根View的DisplayList
        //從此處開(kāi)始將繪制操作記錄到DisplayList里
        //最終記錄在rootRenderNode里
        updateRootDisplayList(view, callbacks);

        //(2)--->渲染
        //渲染繪制的內(nèi)容
        //以proxy為橋梁,而proxy又與rootRenderNode關(guān)聯(lián)
        //因此最終將上一步記錄的繪制操作交給單獨(dú)的線程渲染
        int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
        ...
    }

重點(diǎn)關(guān)注錄制操作過(guò)程,接著來(lái)分析它:

#ThreadedRenderer.java
    private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
        //遍歷ViewTree,構(gòu)建DisplayList
        updateViewTreeDisplayList(view);

        //當(dāng)ViewTree DisplayList構(gòu)建完畢后
        //一開(kāi)始mRootNode 是沒(méi)有DisplayList
        if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
            //申請(qǐng)Canvas
            RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
            try {
                ...
                //view.updateDisplayListIfDirty() 返回的是RootView 關(guān)聯(lián)的renderNode
                //現(xiàn)在將RootView renderNode掛到canvas下,這樣子就串聯(lián)起所有的renderNode了
                canvas.drawRenderNode(view.updateDisplayListIfDirty());
                ...
                mRootNodeNeedsUpdate = false;
            } finally {
                //最后將DisplayList 掛到renderNode下
                mRootNode.endRecording();
            }
        }
    }

    private void updateViewTreeDisplayList(View view) {
        //標(biāo)記該View已繪制過(guò)
        view.mPrivateFlags |= View.PFLAG_DRAWN;
        //mRecreateDisplayList --> 表示該View 是否需要重建DisplayList,也就是重新錄制,更直白地說(shuō)是否需要走Draw 過(guò)程
        //若是打上了 PFLAG_INVALIDATED 標(biāo)記,也就是該View需要刷新,則需要重建
        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                == View.PFLAG_INVALIDATED;
        //清空原來(lái)的值
        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
        //如果有需要,更新View的DisplayList
        view.updateDisplayListIfDirty();
        //View 已經(jīng)重建完畢,無(wú)需再重建
        view.mRecreateDisplayList = false;
    }

以上調(diào)用了到了View里的方法:updateDisplayListIfDirty()。
顧名思義,如果有需要更新View的DisplayList。

#View.java
    public RenderNode updateDisplayListIfDirty() {
        //每個(gè)View構(gòu)造的時(shí)候都會(huì)創(chuàng)建一個(gè)RenderNode:mRenderNode,稱之為渲染節(jié)點(diǎn)
        final RenderNode renderNode = mRenderNode;
        //是否支持硬件加速,通過(guò)判斷View.AttachInfo.mThreadedRenderer
        if (!canHaveDisplayList()) {
            return renderNode;
        }

        //取出該View的標(biāo)記
        //1、繪制緩存失效 2、渲染節(jié)點(diǎn)還沒(méi)有DisplayList 3、渲染節(jié)點(diǎn)有DisplayList,但是需要更新
        //三者滿足其中一個(gè)條件,則進(jìn)入條件代碼塊
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                || !renderNode.hasDisplayList()
                || (mRecreateDisplayList)) {
            //如果有DisplayList且該DisplayList無(wú)需更新,則說(shuō)明該View不需要重新走Draw過(guò)程
            if (renderNode.hasDisplayList()
                    && !mRecreateDisplayList) {
                //標(biāo)記該View已經(jīng)繪制完成且緩存是有效的
                mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                //繼續(xù)查看子布局是否需要構(gòu)建DisplayList
                dispatchGetDisplayList(); //---------(1)

                return renderNode; // no work needed
            }
            
            //上述條件不滿足,則說(shuō)明該View需要構(gòu)建DisplayList
            mRecreateDisplayList = true;
            //layout 過(guò)程確定的View的坐標(biāo)此時(shí)用到了
            int width = mRight - mLeft;
            int height = mBottom - mTop;
            //獲取當(dāng)前設(shè)置的layerType
            int layerType = getLayerType();
            //從renderNode里獲取Canvas對(duì)象,Canvas的尺寸初始化為View的尺寸
            //該Canvas是RecordingCanvas類型,簡(jiǎn)單理解為用來(lái)錄制的Canvas
            final RecordingCanvas canvas = renderNode.beginRecording(width, height);

            try {
                //layerType 有三種取值
                //如果是軟件繪制緩存
                if (layerType == LAYER_TYPE_SOFTWARE) {
                    //---------(2)
                    //則構(gòu)建緩存
                    buildDrawingCache(true);
                    //實(shí)際上就是將繪制操作寫入Bitmap里
                    Bitmap cache = getDrawingCache(true);
                    if (cache != null) {
                        //將該Bitmap繪制到Canvas里
                        canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                    }
                } else {
                    //如果沒(méi)有設(shè)置軟件繪制緩存
                    
                    //一般配合Scroller 滑動(dòng)使用
                    computeScroll();
                    //mScrollX、mScrollY 為滾動(dòng)的距離
                    //當(dāng)mScrollX 為正值時(shí),canvas向左移動(dòng),繪制的內(nèi)容往左移動(dòng),這也就是為什么明明scroll為正值,為啥View內(nèi)容往左移的根本原因
                    canvas.translate(-mScrollX, -mScrollY);
                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;

                    //---------(3)
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        //該View不需要繪制自身內(nèi)容(包括內(nèi)容、前景、背景等)
                        //直接發(fā)起繪制子布局的請(qǐng)求
                        dispatchDraw(canvas);
                        drawAutofilledHighlight(canvas);
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                        if (debugDraw()) {
                            debugDrawFocus(canvas);
                        }
                    } else {
                        //需要繪制自身
                        draw(canvas);
                    }
                }
            } finally {
                //最后結(jié)束canvas錄制,并將錄制產(chǎn)生的結(jié)果:DisplayList交給renderNode
                //---------(4)
                renderNode.endRecording();
                setDisplayListProperties(renderNode);
            }
        } else {
            //三個(gè)條件不滿足,認(rèn)為已經(jīng)繪制完成
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        }
        //返回renderNode 到上一層
        //---------(5)
        return renderNode;
    }

注釋里列出了5個(gè)比較重要的點(diǎn),來(lái)一一解析:
(1)
dispatchGetDisplayList()
該方法在View里沒(méi)有實(shí)現(xiàn),在ViewGroup實(shí)現(xiàn)如下:

#ViewGroup.java
    protected void dispatchGetDisplayList() {
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            //遍歷子布局
            final View child = children[i];
            if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
                //重建子布局DisplayList
                recreateChildDisplayList(child);
            }
        }
        ...
    }

    private void recreateChildDisplayList(View child) {
        //判斷是否需要重建
        child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
        child.mPrivateFlags &= ~PFLAG_INVALIDATED;
        //調(diào)用子布局重建方法
        child.updateDisplayListIfDirty();
        child.mRecreateDisplayList = false;
    }

可以看出,dispatchGetDisplayList 作用:

遍歷子布局,并調(diào)用它們的重建方法

這樣子,從RootView開(kāi)始遞歸調(diào)用updateDisplayListIfDirty(),如果子布局需要重建DisplayList,則重新錄制繪制操作,否則繼續(xù)查找子布局是否需要重建DisplayList。

(2)
buildDrawingCache(xx) 用來(lái)繪制離屏緩存,后續(xù)再細(xì)說(shuō)。

(3)
跳過(guò)繪制這段可參考:Android ViewGroup onDraw為什么沒(méi)調(diào)用

(4)
硬件加速繪制有開(kāi)始、錄制、結(jié)束的標(biāo)記:

1、renderNode生成用來(lái)繪制的Canvas--> beginRecording,此為開(kāi)始。
2、調(diào)用Canvas.drawXX()--> 錄制具體的東西,此為錄制過(guò)程
3、renderNode結(jié)束繪制--> endRecording(),從Canvas里拿到錄制的結(jié)果:DisplayList,并將該結(jié)果賦值給renderNode,此為錄制結(jié)束

(5)
從第4點(diǎn)可以看出,錄制的結(jié)果已經(jīng)存放到RenderNode里,需要將RenderNode返回,該RenderNode將會(huì)被掛到父布局的Canvas里,也就是說(shuō)父布局Canvas已經(jīng)持有了子布局錄制好的DisplayList。

簡(jiǎn)單一些,用圖表示單個(gè)View的硬件加速繪制流程:


image.png

ViewTree 硬件加速過(guò)程:


image.png

很明顯,硬件加速繪制過(guò)程就是構(gòu)建DisplayList過(guò)程,從RootView遞歸子布局構(gòu)建DisplayList,當(dāng)整個(gè)DisplayList構(gòu)建完畢,就可以進(jìn)行渲染了,渲染線程交給GPU處理,這樣子大大解放了CPU工作。

LayerType 對(duì)繪制的影響

以上分別闡述了軟件繪制與硬件加速繪制的流程,分析的起點(diǎn)是該Window是否支持硬件加速而走不同的分支。
從RootView開(kāi)始到遍歷所有的子孫View,要么都是軟件繪制,要么都是硬件加速繪制,如果在硬件加速繪制的中途禁用了某個(gè)View的硬件加速會(huì)如何表現(xiàn)呢?我們之前提到過(guò)通過(guò)設(shè)置View->LayerType來(lái)禁用硬件加速,接下來(lái)分析LayerType對(duì)繪制流程的影響。
Android 自定義View之Draw過(guò)程(上)
分析可知:不管軟件繪制或者硬件加速繪制,都會(huì)走一套公共的流程:

draw(xx)->dispatchDraw(xx)->draw(x1,x2,x3)->draw(xx)...
image.png

這也是遞歸調(diào)用的過(guò)程。
對(duì)于單個(gè)View,軟件繪制與硬件加速分歧點(diǎn)在哪呢?
答案是:draw(x1,x2,x3)方法

View的軟硬繪制分歧點(diǎn)

#View.java
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        //canvas是否支持硬件加速
        //默認(rèn)canvas是不支持硬件加速的
        //RecordingCanvas支持硬件加速
        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();

        //是否使用RenderNode繪制,也就是該View是否支持硬件加速
        //該View支持硬件加速的條件是:canvas支持硬件加速+該Window支持硬件加速
        boolean drawingWithRenderNode = mAttachInfo != null
                && mAttachInfo.mHardwareAccelerated
                && hardwareAcceleratedCanvas;
        //動(dòng)畫相關(guān)
        ...

        if (hardwareAcceleratedCanvas) {
            //canvas支持硬件加速,需要檢測(cè)是否需要重建DisplayList
            mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
            mPrivateFlags &= ~PFLAG_INVALIDATED;
        }

        RenderNode renderNode = null;
        Bitmap cache = null;
        //獲取LayerType,View 默認(rèn)類型是None
        int layerType = getLayerType();
        //------>(1)
        if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
            //1、設(shè)置了離屏軟件繪制緩存 2、View不支持硬件加速繪制
            //兩者滿足其一
            if (layerType != LAYER_TYPE_NONE) {
                //可能設(shè)置了軟件緩存或者硬件緩存
                //此時(shí)硬件緩存當(dāng)做軟件緩存來(lái)使用
                layerType = LAYER_TYPE_SOFTWARE;
                //繪制到軟件緩存
                //------>(2)
                buildDrawingCache(true);
            }
            //取出軟件緩存
            cache = getDrawingCache(true);
        }

        if (drawingWithRenderNode) { //----->(3)
            //該View支持硬件加速
            //則嘗試構(gòu)建DisplayList,并返回renderNode
            renderNode = updateDisplayListIfDirty();
            if (!renderNode.hasDisplayList()) {
                //一般很少走這
                renderNode = null;
                drawingWithRenderNode = false;
            }
        }

        int sx = 0;
        int sy = 0;
        if (!drawingWithRenderNode) {
            computeScroll();
            //不使用硬件加速時(shí)將內(nèi)容偏移記錄
            sx = mScrollX;
            sy = mScrollY;
        }

        //注意這兩個(gè)標(biāo)記,下面會(huì)用到
        //1、存在軟件緩存 2、不支持硬件加速 兩者同時(shí)成立,則說(shuō)明:使用軟件緩存繪制
        final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
        //1、不存在軟件緩存 2、不支持硬件加速,兩者同時(shí)成立,則說(shuō)明:使用軟件繪制
        final boolean offsetForScroll = cache == null && !drawingWithRenderNode;

        if (offsetForScroll) {
            //------>(4)
            //如果是軟件繪制,需要根據(jù)View的偏移與內(nèi)容偏移移動(dòng)canvas
            //此時(shí)包括內(nèi)容滾動(dòng)偏移量
            canvas.translate(mLeft - sx, mTop - sy);
        } else {
            if (!drawingWithRenderNode) {
                //------>(5)
                //如果不支持硬件加速,則說(shuō)明可能是軟件緩存繪制
                //此時(shí)也需要位移canvas,只不過(guò)不需要考慮內(nèi)容滾動(dòng)偏移量
                canvas.translate(mLeft, mTop);
            }
            ...
        }

        ...

        if (!drawingWithRenderNode) {
            //不支持硬件加速
            if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
                //裁減canvas,限制canvas展示區(qū)域,這就是子布局展示為什么不能超過(guò)父布局區(qū)域的原因
                if (offsetForScroll) {
                    //是軟件繪制,則裁減掉滾動(dòng)的距離
                    //------>(6)
                    canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
                } else {
                    //否則無(wú)需考慮滾動(dòng)距離
                    if (!scalingRequired || cache == null) {
                        canvas.clipRect(0, 0, getWidth(), getHeight());
                    } else {
                        canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                    }
                }
            }
            ...
        }

        if (!drawingWithDrawingCache) {
            //不使用軟件緩存繪制
            if (drawingWithRenderNode) {
                //支持硬件加速
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                //將該View的renderNode掛到父布局的Canvas下,此處建立了連接
                ((RecordingCanvas) canvas).drawRenderNode(renderNode);
            } else {
                //軟件繪制,發(fā)起了繪制請(qǐng)求:dispatchDraw(canvas) & draw(canvas);
                //------>(7)
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);
                }
            }
        } else if (cache != null) {
            //軟件繪制緩存存在
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
                ...
                //沒(méi)有設(shè)置緩存類型,則將軟件繪制緩存寫入到canvas的bitmap里
                canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
            } else {
                ...
                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
            }
        }
        ...
        //該View 構(gòu)建完畢
        mRecreateDisplayList = false;
        return more;
    }

該方法里面的判斷比較亂,提取了比較重要的7個(gè)點(diǎn):
(1)
只要設(shè)置了離屏軟件緩存或者不支持硬件加速,那么就需要使用軟件緩存繪制。

(3)
只要支持硬件加速,則使用硬件加速繪制。結(jié)合(1),是不是覺(jué)得有點(diǎn)矛盾呢?想想滿足(1)條件的情況之一:設(shè)置了離屏軟件緩存,也支持硬件加速,按照(1)的邏輯,那么此時(shí)啟用了軟件緩存繪制。那么(3)繼續(xù)用硬件加速繪制不是多此一舉嗎?
回顧一下updateDisplayListIfDirty()里的片段:

        if (layerType == LAYER_TYPE_SOFTWARE) {
            ...
            //軟件緩存繪制
            buildDrawingCache(true);
        } else {
            //硬件繪制
            ...
        }

這里邊再次進(jìn)行了判斷。

(4)(5)
Canvas位移
對(duì)于軟件繪制,將Canvas進(jìn)行位移,位移距離考慮了View本身偏移以及View內(nèi)容偏移。
對(duì)于軟件緩存繪制,將Canvas進(jìn)行位移,僅僅考慮了View本身偏移。
對(duì)于硬件加速繪制,沒(méi)看到對(duì)Canvas進(jìn)行位移。
實(shí)際上針對(duì)軟件緩存繪制與硬件加速繪制,Canvas位移既包括View本身偏移也包含了View內(nèi)容偏移。只是不在上述的代碼里。
對(duì)于軟件緩存繪制:

在buildDrawingCacheImpl(xx) -> canvas.translate(-mScrollX, -mScrollY);進(jìn)行了內(nèi)容偏移。

而對(duì)于硬件加速繪制:

在layout(xx)->mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom) 進(jìn)行了View本身的偏移。
在updateDisplayListIfDirty(xx)->canvas.translate(-mScrollX, -mScrollY);進(jìn)行了內(nèi)容偏移。

因此,不論軟件繪制/軟件緩存繪制/硬件加速繪制,三者都對(duì)Canvas進(jìn)行了位移,位移包括:View本身的偏移以及內(nèi)容的偏移。

以上也解釋了問(wèn)題1。

(6)
Canvas裁減
對(duì)于軟件繪制,Canvas裁減包括了View內(nèi)容偏移。
對(duì)于軟件緩存繪制,Canvas 繪制到Bitmap里。
對(duì)于硬件加速繪制,在setDisplayListProperties(xx)->renderNode.setClipToBounds(xx) 進(jìn)行裁減。
(7)
如果是軟件繪制,那么直接調(diào)用dispatchDraw(xx)/draw(xx)發(fā)起繪制。

draw(x1,x2,x3)方法作用:決定View是使用何種繪制方式:

1、硬件加速繪制
2、軟件繪制
3、軟件緩存繪制

軟件緩存繪制

來(lái)看看如何構(gòu)建軟件緩存:

#View.java
    public void buildDrawingCache(boolean autoScale) {
        //如果緩存標(biāo)記為失效或者緩存為空
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
                mDrawingCache == null : mUnscaledDrawingCache == null)) {
            try {
                //構(gòu)建緩存
                buildDrawingCacheImpl(autoScale);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    }

    private void buildDrawingCacheImpl(boolean autoScale) {
        int width = mRight - mLeft;
        int height = mBottom - mTop;
        ...

        boolean clear = true;
        Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
        //bitmap 不存在或者bitmap與View尺寸不一致,則創(chuàng)建
        ...
        Canvas canvas;
        if (attachInfo != null) {
            canvas = attachInfo.mCanvas;
            if (canvas == null) {
                //第一次,AttachInfo里并沒(méi)有Canvas
                canvas = new Canvas();
            }
            //關(guān)聯(lián)bitmap
            canvas.setBitmap(bitmap);
            attachInfo.mCanvas = null;
        } else {
            //很少走這
            canvas = new Canvas(bitmap);
        }
        computeScroll();
        final int restoreCount = canvas.save();
        //根據(jù)內(nèi)容滾動(dòng)平移
        canvas.translate(-mScrollX, -mScrollY);

        mPrivateFlags |= PFLAG_DRAWN;
        if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
                mLayerType != LAYER_TYPE_NONE) {
            //打上標(biāo)記,說(shuō)明軟件繪制緩存已生效
            mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
        }
        
        //同樣的,調(diào)用公共方法
        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchDraw(canvas);
            drawAutofilledHighlight(canvas);
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().draw(canvas);
            }
        } else {
            draw(canvas);
        }

        canvas.restoreToCount(restoreCount);
        canvas.setBitmap(null);

        if (attachInfo != null) {
            //記錄下來(lái),下次創(chuàng)建直接使用
            attachInfo.mCanvas = canvas;
        }
    }

如此一來(lái),軟件緩存就構(gòu)建完成了,其結(jié)果存儲(chǔ)在Bitmap里,可以通過(guò)如下方法獲?。?/p>

#View.java
    public Bitmap getDrawingCache(boolean autoScale) {
        //禁止使用軟件緩存
        //默認(rèn)不禁止
        if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
            return null;
        }
        if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
            //是否開(kāi)啟了軟件緩存繪制,默認(rèn)不開(kāi)啟
            //構(gòu)建緩存
            buildDrawingCache(autoScale);
        }
        //將緩存返回
        return autoScale ? mDrawingCache : mUnscaledDrawingCache;
    }

該方法可用來(lái)獲取View的頁(yè)面。
做一個(gè)小結(jié):

一開(kāi)始,硬件加速繪制流程和軟件繪制流程各走各的互不影響。
1、使用軟件繪制時(shí)候,設(shè)置了離屏緩存類型:軟件緩存,則軟件繪制失效,僅僅使用軟件緩存繪制。設(shè)置了硬件緩存類型也當(dāng)做軟件緩存繪制。
2、使用硬件加速繪制的時(shí)候,設(shè)置了離屏緩存類型:軟件緩存,則硬件加速繪制失效,僅僅使用軟件緩存繪制。這也就是為什么設(shè)置軟件緩存可以禁用硬件加速的原因。
3、軟件緩存繪制的結(jié)果保存在bitmap里,該Bitmap最終會(huì)繪制到父布局的Canvas里。

不管使用哪種繪制類型,都會(huì)走共同的調(diào)用方法:draw(xx)/dispatchDraw(xx)。
因此,繪制類型對(duì)于我們重寫onDraw(xx)是透明的。

Canvas 從哪里來(lái)到哪里去

軟件繪制
從ViewRootImpl->drawSoftware(xx)開(kāi)始,通過(guò):

canvas = mSurface.lockCanvas(dirty);

生成了Canvas。該Canvas通過(guò)View.draw(xx)方法傳遞給所有的子布局,因此此種情形下,整個(gè)ViewTree共享同一個(gè)Canvas對(duì)象。Canvas類型為:CompatibleCanvas。
硬件加速繪制
從View->updateDisplayListIfDirty(xx)開(kāi)始,通過(guò):

final RecordingCanvas canvas = renderNode.beginRecording(width, height);

生成了Canvas??梢钥闯?,對(duì)于每個(gè)支持硬件加速的View都重新生成了Canvas。Canvas類型為:RecordingCanvas。
軟件緩存繪制
從View->buildDrawingCacheImpl(xx)開(kāi)始,通過(guò):

canvas = new Canvas();

生成了Canvas,并將該Canvas記錄在AttachInfo里,下次再次構(gòu)建該View軟件緩存時(shí)拿出來(lái)使用。可以看出,對(duì)于每個(gè)使用了軟件緩存的View都生成了新的Canvas,當(dāng)然如果AttachInfo有,就可以重復(fù)使用。
脫離View的Canvas
以上三者有個(gè)共同的特點(diǎn):所生成的Canvas最終都與Surface建立了聯(lián)系,因此通過(guò)這些Canvas繪制的內(nèi)容最終能夠展示在屏幕上。
那是否可以直接構(gòu)造脫離View的Canvas呢?答案是可以的。

    private void buildCanvas(int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas();
        canvas.setBitmap(bitmap);
        
        //繪制
        canvas.drawXX(xx);
        ...
    }

如上所示,創(chuàng)建一個(gè)Canvas與Bitmap,并將兩者關(guān)聯(lián)起來(lái)。最后調(diào)用Canvas繪制API,繪制的結(jié)果將保存在Bitmap里。這個(gè)過(guò)程實(shí)際上也是軟件緩存繪制使用的方法。
當(dāng)然拿到了Bitmap后,我們想讓其展示就比較簡(jiǎn)單了,只要讓其關(guān)聯(lián)到View上就可以展示到屏幕上了。關(guān)聯(lián)到View上實(shí)際上就是使用View關(guān)聯(lián)的Canvas將生成的Bitmap繪制其上,

繪制流程全家福

用圖表示繪制流程:

單純的軟件繪制與硬件加速繪制:

image.png

設(shè)置了軟件緩存時(shí)的繪制:
image.png

至此,Draw過(guò)程系列文章結(jié)束。
本文源碼基于Android 10。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容