Android开发一路向下之AOSP研究

View绘制原理 (01)-JAVA层分析

2023-06-25  本文已影响0人  代码多哥

View的绘制是Android的基础知识,本人将从浅入深介绍Android View的绘制流程及原理。本文基于android 12,阐述个人的理解,源码量非常大,主要目的是记录和分享自己的学习心得,如有错误,欢迎同行指正,共同进步。

1. 从onDraw说起

onDaw(Canvas canvas)这个是最简单的绘制方法,是学习自定义控件的基本方法。canvas参数提供了绘制的画布,我们可以重写这个方法,来实现我们的绘制逻辑,比如绘制直线,绘制矩形,绘制图片(canvas的api不是本文的范围,后面会有专门的文章来介绍),这个方法用久了就会产生2个疑问,
(1)这里的canvas是那里来的?
(2)画在canvas上的内容怎么到屏幕上去的?
要回答这两个问题,需要从更深入的系统代码来找到答案,这是本文的主要内容。

2 屏幕刷新流程

onDraw方法是绘制当前View的方法,但是这个方法不需要我们手动去调用,而是在刷新屏幕的某个时刻,被系统调用,从而绘制出内容,然后输出到屏幕,这里我们就从Android java层入手来一步一步到来看看系统是什么时候调用到这个onDraw方法的。从源头上看,这里涉及到两个重要的类 ChoreographerViewRootImpl,这两个类都包含有很多的逻辑功能,View的绘制是其中的一种,因此不在此展开(单独的介绍这个两个类也是可以写一篇文章),这也是本系列文章的原则,即从流程的角度来介绍源码,求证单个流程的原理,而不是从代码的角度分析某个类的全部内容。

2.1 Vsync回调

大家都知道,当屏幕硬件刷新之后Android低层框架会通知到应用层,这个就是Vsync信号,Vsync信号的流程也非常复杂,因此不在此深入介绍。本文主要介绍如何接收到Vsync信号以及收到Vsync之后,是如何刷新屏幕,从而调用到onDraw进行界面绘制的。所以我们可以认为Vsync是整个流程的起点。请看源码:
frameworks/base/core/java/android/view/Choreographer.java

private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        mDisplayEventReceiver = USE_VSYNC
                ? new FrameDisplayEventReceiver(looper, vsyncSource)
                : null;
        ....
    }
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        ....
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
                VsyncEventData vsyncEventData) {
                ...
                Message msg = Message.obtain(mHandler, this);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
        }
    }
void doFrame(long frameTimeNanos, int frame,
            DisplayEventReceiver.VsyncEventData vsyncEventData) {
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos, frameIntervalNanos);
            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
    }
 void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            ...
            final long now = System.nanoTime();
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;
            ...
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                ...
                c.run(frameTimeNanos);
           ...
           do {
                    final CallbackRecord next = callbacks.next;
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
         }
    }
private static final class CallbackRecord {
        public CallbackRecord next;
        public long dueTime;
        public Object action; // Runnable or FrameCallback
        public Object token;

        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }
    }

上述几段代代码是关于刷新的,大部分的方法也比较长,我作了一些删减,将不本文无关的代码,日志和注释省略,以突出流程逻辑。建议读者理解流程后,再去详细看每个方法的细节逻辑,有非常多的if分支,每个分支代表一种场景,另外,源码中有很多使用日志来代替注释的地方,这种做法我个人非常推荐,可以用于到日常的开发中去。简单介绍以下上面的代码

Choreographer中相关的代码就先介绍到这里。后面进入到ViewRootImpl中继续分析。

2.2 ViewRootImpl

public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
        ...
        mChoreographer = useSfChoreographer
                ? Choreographer.getSfInstance() : Choreographer.getInstance();
        ...
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
                ...
                requestLayout();
                ...     
    }
 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
了
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

这部分代码是ViewRootImpl与Choreographer之间的交互,在ViewRootImpl的构造方法中会获取到一个Choreographer对象,他是一个线程本地变量,也就是说在同一个线程内部,会共享一个对象。ViewRootImpl对象生成的地方不属于本文的范围,它是整个窗口视图的根,在生成窗口对象后会调setView方法来设置一个View作为界面的内容,在setView方法中会调用一次requestLayout(当然其他情况调用到requesayout也会走后面相同的处理)此时会向Choreograher post一个Choreographer.CALLBACK_TRAVERSAL类型的callback,这个回调正是上面提到的那个回调对象,因此在sync信号到来后,会得到执行,于是执行mTraversalRunnable.run, 最后进入到doTravasal, 最终进入到本文的重点内容---performTraversals方法。 可以说上面的内容主要是介绍View绘制的前提铺设,我们理解了上述的流程就能对的View的绘制有一个全面的认识。

3. performTravasals

了解了前面的知识之后,我们就可以放心的去看View是如何绘制出来了,performTraversals是在一帧中作的所有事情,主要来看,包含准备surface ,performMeasure,performLayout和performDraw三个方法。

private void performTraversals() {
                ...
               relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
              if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC) != 0) {
                   if (DEBUG_BLAST) {
                       Log.d(mTag, "Relayout called with blastSync");
                   }
                   reportNextDraw();
               ....
               performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
               ...
               performLayout(lp, mWidth, mHeight);
               ...
               performDraw();
               ...
}

这个方法的复杂是非常高的,上面省略了各种复杂的逻辑判断,仅列出正常情况下执行到的主要方法。

4. performDraw

    private void performDraw() {
       
        boolean canUseAsync = draw(fullRedrawNeeded);
          
        if (mReportNextDraw) {
            mReportNextDraw = false;
            ...
            if (mSurfaceHolder != null && mSurface.isValid()) {
                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);
                SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();

                sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
            } 
           ...
        }
    }
    private void postDrawFinished() {
        mHandler.sendEmptyMessage(MSG_DRAW_FINISHED);
     }

    void pendingDrawFinished() {
        if (mDrawsNeededToReport == 0) {
            throw new RuntimeException("Unbalanced drawPending/pendingDrawFinished calls");
        }
        mDrawsNeededToReport--;
        if (mDrawsNeededToReport == 0) {
            reportDrawFinished();
        } else if (DEBUG_BLAST) {
            Log.d(mTag, "pendingDrawFinished. Waiting on draw reported mDrawsNeededToReport="
                    + mDrawsNeededToReport);
        }
    }
    private void reportDrawFinished() {
        try {
            if (DEBUG_BLAST) {
                Log.d(mTag, "reportDrawFinished");
            }
            mDrawsNeededToReport = 0;
            mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction);
        } catch (RemoteException e) {
            Log.e(mTag, "Unable to report draw finished", e);
            mSurfaceChangedTransaction.apply();
        } finally {
            mSurfaceChangedTransaction.clear();
        }
    }

上述代码是执行绘制的主要流程,他主要包括绘制和结束绘制两部分。

5. draw

private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        if (!surface.isValid()) {
            return false;
        }
        ...
        boolean useAsyncReport = false;
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty || mNextDrawUseBlastSync) {
            if (isHardwareEnabled()) {
               ...
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            } else {
               ...
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
            }
        }

        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
        return useAsyncReport;
    }

在这里可以看到,draw的前提条件是存在有效的surface,这个是在relayoutWindow的时候已经确保的了,所以方法可以继续执行,在接着会判断是否开启了硬件加速,如果开启话,先计算后设置invalidateRoot 为true,然后通过mAttachInfo.mThreadedRender.draw(view,attachInfo,DrawCallback)来绘制这个mView。否则使用软件绘制drawSoftware。因为现在基本都是使用硬件绘制,因此我们主要看看ThreadedRender是如何来draw这个View的

6. Hardware Drawing

void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
        attachInfo.mViewRootImpl.mViewFrameInfo.markDrawStart();

        updateRootDisplayList(view, callbacks);
        final FrameInfo frameInfo = attachInfo.mViewRootImpl.getUpdatedFrameInfo();

        int syncResult = syncAndDrawFrame(frameInfo);
        ...
    }

这里的代码会稍微有一点长,部分方法并没有省略,因此此时是比较细节的地方,尽量详细一点。

 private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
        updateViewTreeDisplayList(view);

        if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
            RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
            ...
            canvas.drawRenderNode(view.updateDisplayListIfDirty());
            ...
            mRootNodeNeedsUpdate = false;
            ...
            mRootNode.endRecording(); 
        }
    }

 private void updateViewTreeDisplayList(View view) {
        view.mPrivateFlags |= View.PFLAG_DRAWN;
        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                == View.PFLAG_INVALIDATED;
        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
        view.updateDisplayListIfDirty();
        view.mRecreateDisplayList = false;
    }

首先调用updateViewTreeDisplayList对UI树上的所有元素调用view.updateDisplayListIfDirty计算需要更新的DisplayList,计算完毕后,因为mRootNodeNeedsUpdate == true,所以会对RootView进行重新绘制,这里会用RendderNode.beginRecord方法生成一个RecordingCanvas, 然后调用canvas.drawRenderNode(view.updateDisplayListIfDirty())来绘制一个RenderNode,(view.updateDisplayListIfDirty()会返回这个View已经更新后的renderNode,RenderNode是一个绘制节点,每个View都有一个renderNode成员变量)这几个类都是native实现的,暂时不分析。RecordingCanvas.drawRenderNode是这个里的关键方法。这里我们看到这个RecordingCanvas,我们可能会联想到是不是就是我们要找的那个canvas,但是这里我们只看到了rootView的绘制通过canvas.drawRenderNode就直接绘制了,并没有看到有调用rootView.onDraw(canvas)这样的代码。因此,我们需要进一步来看一下updateViewTreeDisplayList 这个方法,这里面有一些flag的计算

 public RenderNode updateDisplayListIfDirty() {
        final RenderNode renderNode = mRenderNode;
        if (!canHaveDisplayList()) {
            // can't populate RenderNode, don't try
            return renderNode;
        }

        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || !renderNode.hasDisplayList() || (mRecreateDisplayList)) {
          
            if (renderNode.hasDisplayList()&& !mRecreateDisplayList) {
                ...
                dispatchGetDisplayList();
                return renderNode; // no work needed
            }

            final RecordingCanvas canvas = renderNode.beginRecording(width, height);
            if (layerType == LAYER_TYPE_SOFTWARE) {
                    buildDrawingCache(true);
                    Bitmap cache = getDrawingCache(true);
                    if (cache != null) {
                        canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                    }
             } else {        
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        dispatchDraw(canvas);
                        ...
                    } else {
                        draw(canvas);
                    }
          }
           
        return renderNode;
    }
 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)) {
                recreateChildDisplayList(child);
            }
        }
        final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();
        for (int i = 0; i < transientCount; ++i) {
            View child = mTransientViews.get(i);
            if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
                recreateChildDisplayList(child);
            }
        }
        if (mOverlay != null) {
            View overlayView = mOverlay.getOverlayView();
            recreateChildDisplayList(overlayView);
        }
        if (mDisappearingChildren != null) {
            final ArrayList<View> disappearingChildren = mDisappearingChildren;
            final int disappearingCount = disappearingChildren.size();
            for (int i = 0; i < disappearingCount; ++i) {
                final View child = disappearingChildren.get(i);
                recreateChildDisplayList(child);
            }
        }
    }

    private void recreateChildDisplayList(View child) {
        child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
        child.mPrivateFlags &= ~PFLAG_INVALIDATED;
        child.updateDisplayListIfDirty();
        child.mRecreateDisplayList = false;
    }

对于ViewGroup来说,直接会管理在View层级中的children这些子控件,还有层级外的transientViews,overay,disappearingChildren, 因此分别遍历这些控件,然后调用recreateChildDisplayList, 然后到用child.updateDisplayListIfDirty,这个方法在上面已经介绍过了,没有发生变化的View不会执行任何draw

7. View.draw

从上面的分析中我看调用View.draw的地方,是在updateDisplayListIfDirtry的时候,如果这个这个View需要重新绘制,如果这个View设置的layer是LAYER_TYPE_SOFTWARE,则会使用一个普通的基于Bitmap的Canvas来传给View来绘制,其他layer就是使用RenderNode.beginRecordings生成的RecordingCavas来绘制。但是绘制的时候,又根据是否需要绘制自身,进入到了draw和dispatchDraw,而不是直接去的onDraw。对于View来说,没有分发的对象,他的dispatchDaw会一个空方法,因此什么都不做,因此大部分View是需要绘制的,从而进入到draw方法。

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
        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
            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);

            if (isShowingLayoutBounds()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }

        // Step 3, draw the content
        onDraw(canvas);

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

        //draw Edge Fade is omitted here 
      
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        onDrawForeground(canvas);

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

        if (isShowingLayoutBounds()) {
            debugDrawFocus(canvas);
        }
    }

细节代码比较多,总的说来是分两种场景,是否需要绘制四边的fade效果。如果没有fade效果就很简单。按照这个顺序执行

但是如果是需要绘制fade效果的情况,在dispatchDraw之后,在绘制四边的fade效果,之后再绘制overlay和滚动条和前景。fade的效果。
了解了View的绘制逻辑之后,我们肯定也会好奇,ViewGroup是什么情况。从前面的分析可以看到,如果ViewGroup也需要绘制自身的情况,它是和View的流程就是一样的,否则ViewGroup的draw方法就并不会被调用,因此onDraw也不会被调用,而是直接进入到调用dispatchDraw。(当然当调用draw的case后面也会调用到dispatchDraw),因为我们来继续看看ViewGroup的绘制方法dispatchDraw

protected void dispatchDraw(Canvas canvas) {
        ...
        final ArrayList<View> preorderedList = isHardwareAccelerated() ? null : buildOrderedChildList();
        final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();

        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
               ...
            }

            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }

        while (transientIndex >= 0) {
            // there may be additional transient views after the normal views
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||transientChild.getAnimation() != null) {
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            ...
         }

        if (mDisappearingChildren != null) {
            final ArrayList<View> disappearingChildren = mDisappearingChildren;
            final int disappearingCount = disappearingChildren.size() - 1;
            // Go backwards -- we may delete as animations finish
            for (int i = disappearingCount; i >= 0; i--) {
                final View child = disappearingChildren.get(i);
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        ...
    }

这里的逻辑非常多,总体来看,我们需要关注几个内容

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

内部逻辑不复杂,直接调用View.draw(canvas,this, drawingTime).。这里需要注意一下,这个并不是View.draw(canvas),因为他们的参数不一样。这就很好奇了,按照现有知识,直接调用child.draw(canvas)不就可以了吗? 或者是不是简单的重载而已?答案是NO,它是一个完全不一样,而且非常重要且复杂的方法。我们先来看看这个View.draw(canvas,this, drawingTime)方法

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
        boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHardwareAccelerated&& hardwareAcceleratedCanvas;
         ...
        if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
             if (layerType != LAYER_TYPE_NONE) {
                 // If not drawing with RenderNode, treat HW layers as SW
                 layerType = LAYER_TYPE_SOFTWARE;
                 buildDrawingCache(true);
            }
            cache = getDrawingCache(true);
        }

        final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;

        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.hasDisplayList()) {
                // 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;
                ((RecordingCanvas) 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);
                }
            }
        } else if (cache != null) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
                // no layer paint, use temporary paint to draw bitmap
                Paint cachePaint = parent.mCachePaint;
                ...
                canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
            } else {
                ...
                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
               ...
            }
        }
        ...
    }

代码逻辑非常多,我先直接总结以下,它决定这个View在作为ViewGroup的child时候如何去绘制自己。它主要也是根据是否开启硬件加速,使用不同的缓存,如果是software drawing,它的缓存(就是上一次绘制的内容)就是一个bitmap,如果在没有更新的情况,直接将这个bitmap画到画布上。也就不再执行onDraw进行绘制,如果没有缓存或者属性变化需要重建的时候,会先去建立这个缓存

 private void buildDrawingCacheImpl(boolean autoScale) {
        ....
           
       bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
                        width, height, quality);
       bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
         
        Canvas canvas;
        if (attachInfo != null) {
            canvas = attachInfo.mCanvas;
            if (canvas == null) {
                canvas = new Canvas();
            }
            canvas.setBitmap(bitmap);
            // Temporarily clobber the cached Canvas in case one of our children
            // is also using a drawing cache. Without this, the children would
            // steal the canvas by attaching their own bitmap to it and bad, bad
            // thing would happen (invisible views, corrupted drawings, etc.)
            attachInfo.mCanvas = null;
        } else {
            // This case should hopefully never or seldom happen
            canvas = new Canvas(bitmap);
        }
        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);
        }

这里可以看到调用View的draw或者dispatchDraw来绘制View到bitmap上。software的case分析完毕。下来看看硬件加速的情况。

 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.hasDisplayList()) {
                // 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;
                ((RecordingCanvas) canvas).drawRenderNode(renderNode);
}

这里看到,硬件加速的时候drawChild会将canvas强制转换成RecordingCanvas然后在调用drawRenderNode(renderNode) 来将当前的View绘制出来,而这个renderNode是上面调用updateDisplayListIfDirty()生成的。里面包含有重用DisplayList的逻辑,如果没有更新不会调用到onDraw

最后drawChild就分析完了,其实它的部分逻辑是和updateDisplayListIfDirty差不多的,只不过drawChild是在拿到Canvas之后决定那些dirty的需要绘制到canvas,而updateDisplayListIfDirty是决定那些dirty绘制到renderNode。到这里 View.onDraw如何被执行的就清楚了,整个绘制流程也就梳理完了。本人认为从View/ViewGroup的角度看,虽然draw(canvas)方法决定了绘制的流水线,但drawChildupdateDisplayListIfDirty才是绘制中最关键的方法,它们包含的缓存逻辑对性能优化起决定作用

8.结语

8.1 答案揭晓:

1)Canvas 是哪里来的? 在未开启硬件加速的情况下,是直接构造的Canvas对象,并将内容画到一个Bitmap缓存;在开启硬件加速的情况下,是来自于RenderNode.begingRecording方法生成的一个RecordingCanvas,并将绘制命令记录到DisplayList
2)绘制完毕后如何显示在界面去?绘制完毕后,ViewRootImpl会调用reportDrawFinished呼叫WMS的远程方法finishDrawing通知绘制完毕,然后进行surfaceflinger合成后显示到屏幕

8.2 绘制流程

8.3 缓存

ViewGroup的drawChild 和 View的updateDisplayListDirty 都使用了缓存来优化,当一个View没有发生变化时,直接使用上一次绘制的内容绘制到父容器中去,这是绘制流程中重点和难点。

8.4 JNI

绘制中大量的实现实质都是使用native去实现的,比如RenderNode,RecordingCanvas,DisplayList 等,另外也与WMS 和 SurfaceFlinger等系统服务关系密切。要更深入的理解绘制流程,请关注后续对JNI的一些解读。

上一篇下一篇

猜你喜欢

热点阅读