赞
踩
View的绘制是Android的基础知识,本人将从浅入深介绍Android View的绘制流程及原理。本文基于android 12,阐述个人的理解,源码量非常大,主要目的是记录和分享自己的学习心得,如有错误,欢迎同行指正,共同进步。
onDaw(Canvas canvas)这个是最简单的绘制方法,是学习自定义控件的基本方法。canvas参数提供了绘制的画布,我们可以重写这个方法,来实现我们的绘制逻辑,比如绘制直线,绘制矩形,绘制图片(canvas的api不是本文的范围,后面会有专门的文章来介绍),这个方法用久了就会产生2个疑问,
(1)这里的canvas是那里来的?
(2)画在canvas上的内容怎么到屏幕上去的?
要回答这两个问题,需要从更深入的系统代码来找到答案,这是本文的主要内容。
onDraw方法是绘制当前View的方法,但是这个方法不需要我们手动去调用,而是在刷新屏幕的某个时刻,被系统调用,从而绘制出内容,然后输出到屏幕,这里我们就从Android java层入手来一步一步到来看看系统是什么时候调用到这个onDraw方法的。从源头上看,这里涉及到两个重要的类 Choreographer和ViewRootImpl,这两个类都包含有很多的逻辑功能,View的绘制是其中的一种,因此不在此展开(单独的介绍这个两个类也是可以写一篇文章),这也是本系列文章的原则,即从流程的角度来介绍源码,求证单个流程的原理,而不是从代码的角度分析某个类的全部内容。
大家都知道,当屏幕硬件刷新之后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中继续分析。
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的绘制有一个全面的认识。
了解了前面的知识之后,我们就可以放心的去看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(); ... }
这个方法的复杂是非常高的,上面省略了各种复杂的逻辑判断,仅列出正常情况下执行到的主要方法。
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();
}
}
上述代码是执行绘制的主要流程,他主要包括绘制和结束绘制两部分。
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的
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; }
canHaveDisplayList:指的是没有开启硬件加速,这里肯定是false,继续往下走
mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || !renderNode.hasDisplayList() || (mRecreateDisplayList)
因为没有进入上面的case,所以继续执行,这里就到了一个关键的地方 final RecordingCanvas canvas = renderNode.beginRecording(width, height); 再次生成一个recordingCanvas,然后再根据当前View layerType在软件还是硬件进行绘制,虽然软件绘制以及经不常用了但是我们可以来对比一下
mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW,是否需要绘制View本身,如果需要绘制,则调用draw,否则调用dispatchDraw。对一单个View而言,绝大多数是需要绘制本身的,但是对于容器类的ViewGroup,基本上是不需要绘制自身的,直接dispatchDraw去绘制children,所以这个开关位对于ViewGroup而言,默认是开启的,只重写ViewGroup的onDraw,自定义的绘图是无效果的。
标记缓存有效,绘制完了,调用renderNode.endRecording。 这是必须的,未结束记录的renderNode不能开启下一次记录。
如果一个View已经绘制过了,而且之后没有属性发生变化,那它在之后遍历到它时,直接使用之前的RenderNode。
另外一种场景是,如果这个View的缓存已经失效了,但是他自己存在DisplayList,但是还不需要重建DisplayList的时候,这个时候不需要绘制自身,但是需要去遍历child,为那些变化了的childView重建DisplayList,即dispatchGetDisplayList()。这种情况是因为一个ViewGroup的某child的属性变化了,因此导致这个child所有的父容器进行invalidate的情况,这时只需要这个child重新执行onDraw。请看下面ViewGroup重写的dispatchGetDisplayList方法:
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
从上面的分析中我看调用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)方法决定了绘制的流水线,但drawChild和updateDisplayListIfDirty才是绘制中最关键的方法,它们包含的缓存逻辑对性能优化起决定作用
1)Canvas 是哪里来的? 在未开启硬件加速的情况下,是直接构造的Canvas对象,并将内容画到一个Bitmap缓存;在开启硬件加速的情况下,是来自于RenderNode.begingRecording方法生成的一个RecordingCanvas,并将绘制命令记录到DisplayList
2)绘制完毕后如何显示在界面去?绘制完毕后,ViewRootImpl会调用reportDrawFinished呼叫WMS的远程方法finishDrawing通知绘制完毕,然后进行surfaceflinger合成后显示到屏幕
从整体看,可以分成
从View本身来看,主要可以分成:
ViewGroup的drawChild 和 View的updateDisplayListDirty 都使用了缓存来优化,当一个View没有发生变化时,直接使用上一次绘制的内容绘制到父容器中去,这是绘制流程中重点和难点。
绘制中大量的实现实质都是使用native去实现的,比如RenderNode,RecordingCanvas,DisplayList 等,另外也与WMS 和 SurfaceFlinger等系统服务关系密切。要更深入的理解绘制流程,请关注后续对JNI的一些解读。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。