view draw aosp15

基础/背景知识

如何理解Drawable?

在 Android 中,Drawable 是一个抽象的概念,表示可以绘制到屏幕上的内容。
它可以是位图图像、矢量图形、形状、颜色等。
Drawable 本身并不是一个 View,它不能直接添加到布局中,而是作为 View 的一部分进行绘制,例如作为 View 的背景、图标或前景。

Drawable 的主要作用是提供一种与 View 分离的图形绘制机制,这样可以将图形资源与 View 的逻辑代码分离,提高代码的可维护性和可重用性。

例如, 您可以将一个 Drawable 设置为多个 View 的背景,而无需为每个 View 都编写相同的绘制代码。

Drawable 的类型:Android 提供了多种类型的 Drawable,例如: BitmapDrawable: 用于显示位图图像,例如 PNG、JPG 或 GIF 文件。
ShapeDrawable: 用于绘制各种形状,例如矩形、圆形、椭圆形等。
LayerDrawable: 用于将多个 Drawable 堆叠在一起,形成一个复合 Drawable。
StateListDrawable: 用于根据 View 的状态显示不同的 Drawable,例如按钮的按下状态和正常状态。
VectorDrawable: 用于显示矢量图形,例如 SVG 文件。

Drawable 的使用方法:可以通过以下方式使用 Drawable:
在 XML 中定义: 将 Drawable 资源文件放在 res/drawable 目录下,然后在 XML 布局文件中引用它。
在代码中创建: 使用 Drawable 类的构造函数或静态方法创建 Drawable 对象。
从资源文件中加载:使用 getResources().getDrawable() 方法从资源文件中加载 Drawable 对象。

Drawable 的 draw() 方法如何将内容绘制到屏幕上?Drawable 的 draw() 方法是将 Drawable 内容绘制到 Canvas 上的关键方法。当 View 需要绘制 Drawable 时,它会调用 Drawable 的 draw() 方法,并将 Canvas 对象作为参数传递给它。Drawable 的 draw() 方法会根据 Drawable 的类型和属性,将内容绘制到 Canvas 上。

onDraw(Canvas canvas)简单理解

调用drawable的draw方法完成具体的绘制;
是最简单的绘制方法,是学习自定义控件的基本方法。canvas参数提供了绘制的画布,可以重写这个方法,来实现我们的绘制逻辑,比如绘制直线,绘制矩形,绘制图片

onDraw的调用时机:屏幕刷新

先找个调用栈,我们跟着看,先从调用链路最下面的android.view.Choreographer$FrameHandler.handleMessage开始;

"main@21848" prio=5 tid=0x2 nid=NA runnablejava.lang.Thread.State: RUNNABLEat android.widget.TextView.onDraw(TextView.java:8302)at android.view.View.draw(View.java:23444)at android.view.View.updateDisplayListIfDirty(View.java:22307)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4552)at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4525)at android.view.View.updateDisplayListIfDirty(View.java:22259)at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:703)at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:709)at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:807)at android.view.ViewRootImpl.draw(ViewRootImpl.java:4823)at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4525)at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3718)at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2437)at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9398)at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1693)at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1701)at android.view.Choreographer.doCallbacks(Choreographer.java:1288)at android.view.Choreographer.doFrame(Choreographer.java:1195)at android.view.Choreographer$FrameHandler.handleMessage(Choreographer.java:1593)at android.os.Handler.dispatchMessage(Handler.java:106)at android.os.Looper.loopOnce(Looper.java:237)at android.os.Looper.loop(Looper.java:339)at android.app.ActivityThread.main(ActivityThread.java:8750)at java.lang.reflect.Method.invoke(Method.java:-1)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:573)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:951)

屏幕硬件刷新-> 通知应用刷新,通过Vsync信号,源码中从何处调用通知的呢?进而调用到onDraw进行界面绘制

Choreographer

Choreographer 是 Android UI 系统中一个重要的组件,它负责协调动画、输入和绘制事件的发生时间, 以确保 UI 的流畅性和响应性。它采用了单例模式,并通过与 VSYNC 信号同步、安排回调函数和管理帧绘制来实现其功能。
其路径是:
frameworks/base/core/java/android/view/Choreographer.java
这里的屏幕刷新也与他有关
在这里插入图片描述
Choreographer中的frameDisplayEventReceiver接受vsync回调,发送消息给this。执行自己runable的run方法
在这里插入图片描述
在这里插入图片描述
doFrame逻辑:
逻辑比较多,调用到doCallbacks中的 TRAVERSAL逻辑
在Choreographer里面,总共4种callback,分别是Choreographer.CALLBACK_INPUT,Choreographer.CALLBACK_INSETS_ANIMATION,Choreographer.CALLBACK_TRAVERSAL,Choreographer.CALLBACK_COMMIT。这4种回调是按照优先级先后进行调用,所有在一个次屏幕刷新中,最先处理的是Input回调,然后是Animation回调,然后是Traversal回调,这个Traversal回调指的就是进行UI元素遍历更新。类型的回调是在ViewRootImpl中注册的。
在这里插入图片描述
doCallbacks中会先拿到全部的callbacks,按照类型进行处理,callbacks的add过程在viewrootimpl中。
在这里插入图片描述
一次调用每个callback的run方法。调用到CallbackRecord中的run方法,先调用下面的,再调用上面的FRAME_CALLBACK_TOKEN调用action的都Frame;
在这里插入图片描述
根据堆栈最终是调用到了viewrootimpl中的
在这里插入图片描述
这里的调用只有一个地方
在这里插入图片描述打印了一下堆栈,点击一个按钮对应的效果,这里可以看到
基本符合直观的逻辑,只不过对于down事件和up事件链路不太一样,不过只要发生view变化,都会触发到这里;

"main@21864" prio=5 tid=0x2 nid=NA runnablejava.lang.Thread.State: RUNNABLEat android.view.ViewRootImpl.scheduleTraversals(ViewRootImpl.java:2411)at android.view.ViewRootImpl.invalidate(ViewRootImpl.java:2065)at android.view.ViewRootImpl.onDescendantInvalidated(ViewRootImpl.java:2058)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.invalidateChild(ViewGroup.java:6113)at android.view.View.invalidateInternal(View.java:19458)at android.view.View.invalidate(View.java:19388)at android.view.View.invalidateDrawable(View.java:24190)at android.widget.TextView.invalidateDrawable(TextView.java:8137)at android.graphics.drawable.Drawable.invalidateSelf(Drawable.java:473)at android.graphics.drawable.RippleDrawable.invalidateSelf(RippleDrawable.java:1024)at android.graphics.drawable.RippleDrawable.startPatternedAnimation(RippleDrawable.java:839)at android.graphics.drawable.RippleDrawable.setRippleActive(RippleDrawable.java:356)at android.graphics.drawable.RippleDrawable.onStateChange(RippleDrawable.java:339)at android.graphics.drawable.Drawable.setState(Drawable.java:836)at android.view.View.drawableStateChanged(View.java:24367)at android.widget.TextView.drawableStateChanged(TextView.java:6218)at androidx.appcompat.widget.AppCompatButton.drawableStateChanged(AppCompatButton.java:170)at android.view.View.refreshDrawableState(View.java:24449)at com.google.android.material.button.MaterialButton.refreshDrawableState(MaterialButton.java:494)at android.view.View.setPressed(View.java:13077)at com.google.android.material.button.MaterialButton.setPressed(MaterialButton.java:1306)at android.view.View.setPressed(View.java:13055)at android.view.View.onTouchEvent(View.java:16634)at android.widget.TextView.onTouchEvent(TextView.java:11544)at android.view.View.dispatchTouchEvent(View.java:15134)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3144)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2763)at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:553)at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1912)at android.app.Activity.dispatchTouchEvent(Activity.java:4277)at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:70)at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:511)at android.view.View.dispatchPointerEvent(View.java:15393)at android.view.ExtViewRootImplImpl.onDispatchPointerEvent(ExtViewRootImplImpl.java:62)at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6870)at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6667)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6118)at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6175)at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6141)at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:6306)at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6149)at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:6363)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6122)at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:6175)at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:6141)at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:6149)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:6122)at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:9294)at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:9235)at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:9194)at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:9430)at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:319)at android.os.MessageQueue.nativePollOnce(MessageQueue.java:-1)at android.os.MessageQueue.next(MessageQueue.java:335)at android.os.Looper.loopOnce(Looper.java:190)at android.os.Looper.loop(Looper.java:339)at android.app.ActivityThread.main(ActivityThread.java:8750)at java.lang.reflect.Method.invoke(Method.java:-1)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:573)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:951)
"main@21869" prio=5 tid=0x2 nid=NA runnablejava.lang.Thread.State: RUNNABLEat android.view.ViewRootImpl.scheduleTraversals(ViewRootImpl.java:2409)at android.view.ViewRootImpl.invalidate(ViewRootImpl.java:2065)at android.view.ViewRootImpl.onDescendantInvalidated(ViewRootImpl.java:2058)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.onDescendantInvalidated(ViewGroup.java:6095)at android.view.ViewGroup.invalidateChild(ViewGroup.java:6113)at android.view.View.invalidateInternal(View.java:19458)at android.view.View.invalidate(View.java:19388)at android.view.View.invalidateDrawable(View.java:24190)at android.widget.TextView.invalidateDrawable(TextView.java:8137)at android.graphics.drawable.Drawable.invalidateSelf(Drawable.java:473)at android.graphics.drawable.RippleDrawable.invalidateSelf(RippleDrawable.java:1024)at android.graphics.drawable.RippleDrawable.exitPatternedAnimation(RippleDrawable.java:844)at android.graphics.drawable.RippleDrawable.setRippleActive(RippleDrawable.java:358)at android.graphics.drawable.RippleDrawable.onStateChange(RippleDrawable.java:339)at android.graphics.drawable.Drawable.setState(Drawable.java:836)at android.view.View.drawableStateChanged(View.java:24367)at android.widget.TextView.drawableStateChanged(TextView.java:6218)at androidx.appcompat.widget.AppCompatButton.drawableStateChanged(AppCompatButton.java:170)at android.view.View.refreshDrawableState(View.java:24449)at com.google.android.material.button.MaterialButton.refreshDrawableState(MaterialButton.java:494)at android.view.View.setPressed(View.java:13077)at com.google.android.material.button.MaterialButton.setPressed(MaterialButton.java:1306)at android.view.View$UnsetPressedState.run(View.java:29921)at android.os.Handler.handleCallback(Handler.java:942)at android.os.Handler.dispatchMessage(Handler.java:99)at android.os.Looper.loopOnce(Looper.java:237)at android.os.Looper.loop(Looper.java:339)at android.app.ActivityThread.main(ActivityThread.java:8750)at java.lang.reflect.Method.invoke(Method.java:-1)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:573)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:951)

接下来就是看最核心的
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:807)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:4823)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4525)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3718)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2437)

ViewRootImpl.performTraversals

doTraversal->performTraversals 是非常核心的逻辑

    void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}performTraversals();if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}}

在performTraversals中,逻辑特别多,这里只针对draw相关的去看
之前在实际工作中看到过这里其实和窗口、touch等等都有相关逻辑的处理。
比如

  • performMeasure和performLayout这种对view的布局等等,会调用应用自己的onMeasure和onlayout等等;
  • relayoutWindow和windowmanagerservice通信,进行窗口的添加等等;

下面开始看重点 performDraw执行绘制

performTraversals->performDraw

在performTraversals中performDraw的调用。
在这里插入图片描述

performDraw->draw

在这里插入图片描述

draw函数看起来就没那么长了,看一下看起来比较重要的逻辑

  1. 检查surface + trackFps
    看起来surface在之前已经ready了;
    在这里插入图片描述
  2. 一大堆其他逻辑,先不看(有个dirty的逻辑,看起来挺重要的)
  3. 判断硬件绘制 + 一堆东西 + draw
    这里面draw的一行
    mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            if (isHardwareEnabled()) {// If accessibility focus moved, always invalidate the root.boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;mInvalidateRootRequested = false;// Draw with hardware renderer.mIsAnimating = false;if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {mHardwareYOffset = yOffset;mHardwareXOffset = xOffset;invalidateRoot = true;}if (invalidateRoot) {mAttachInfo.mThreadedRenderer.invalidateRoot();}dirty.setEmpty();// Stage the content drawn size now. It will be transferred to the renderer// shortly before the draw commands get send to the renderer.final boolean updated = updateContentDrawBounds();if (mReportNextDraw) {// report next draw overrides setStopped()// This value is re-sync'd to the value of mStopped// in the handling of mReportNextDraw post-draw.mAttachInfo.mThreadedRenderer.setStopped(false);}if (updated) {requestDrawWindow();}useAsyncReport = true;if (mHdrRenderState.updateForFrame(mAttachInfo.mDrawingTime)) {final float renderRatio = mHdrRenderState.getRenderHdrSdrRatio();applyTransactionOnDraw(mTransaction.setExtendedRangeBrightness(getSurfaceControl(), renderRatio,mHdrRenderState.getDesiredHdrSdrRatio()));mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(renderRatio);}if (activeSyncGroup != null) {registerCallbacksForSync(syncBuffer, activeSyncGroup);if (syncBuffer) {mAttachInfo.mThreadedRenderer.forceDrawNextFrame();}} else if (mHasPendingTransactions) {// Register a callback if there's no sync involved but there were calls to// applyTransactionOnDraw. If there is a sync involved, the sync callback will// handle merging the pending transaction.registerCallbackForPendingTransactions();}long timeNs = SystemClock.uptimeNanos();mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);// Only trigger once per {@link ViewRootImpl} instance.if (mAppStartInfoTimestampsFlagValue && mRenderThreadDrawStartTimeNs == -1) {mRenderThreadDrawStartTimeNs = timeNs;}

后面判断软件绘制 + draw(不是重点,先不看了)

VRI.draw-> ThreadRenderer.draw

在这里插入图片描述

  1. 核心逻辑:updateRootDisplayList + updateDisplayListIfDirty
    调用updateViewTreeDisplayList对UI树上的所有元素调用view.updateDisplayListIfDirty
    mRootNode.beginRecording生成一个RecordingCanvas,然后调用drawRenderNode

updateViewTreeDisplayList这个函数更新了当前viewtree的 displayList,其实里面没有很多其他逻辑 ,只是搞了一堆flag,用来判断是否需要update,如果需要后面会在dirty中更新

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);try {final int saveCount = canvas.save();canvas.translate(mInsetLeft, mInsetTop);callbacks.onPreDraw(canvas);canvas.enableZ();canvas.drawRenderNode(view.updateDisplayListIfDirty());canvas.disableZ();callbacks.onPostDraw(canvas);canvas.restoreToCount(saveCount);mRootNodeNeedsUpdate = false;} finally {mRootNode.endRecording();}}Trace.traceEnd(Trace.TRACE_TAG_VIEW);}

updateViewTreeDisplayList

中通过View.PFLAG_DRAWN设置为已经绘制
如果这个View调过invlidate,则需要重新创建displayList,
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED,因为计算出了mRecreateDisplayList,重置这个flag
mRecreateDisplayList为false则后面这个view的 displaylist不需要重新生成了;

    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;}

updateDisplayListIfDirty()

对于dirty的view则进行更新这个displayList

/*** Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)* @hide*/@NonNull@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)public RenderNode updateDisplayListIfDirty() {final RenderNode renderNode = mRenderNode;
硬件加速:if (!canHaveDisplayList()) {// can't populate RenderNode, don't tryreturn renderNode;}
缓存无效 || 没有过displayList || 需要重新生成displayListif ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0|| !renderNode.hasDisplayList()|| (mRecreateDisplayList)) {// Don't need to recreate the display list, just need to tell our// children to restore/recreate theirs第一种情况:不需要重新生成displayList(第一次不进入)第二种情况:不是第一次,进入这里的时候cache失效为0,但是有list,但是不需要重新生成的情况;这种情况是不需要绘制自身,需要但是需要调用dispatchGetDisplayList去遍历child,为变化的child重新生成list,这是由于viewgroup某个child属性变化,导致父容器进行invalidate,此时只需要这个child重新执行ondraw;if (renderNode.hasDisplayList()&& !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;
...重新创建一个RecordingCanvasfinal 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 {  硬件绘制// Fast path for layouts with no backgrounds是否需要绘制自身(一般view group不需要绘制自己,则直接dispatch,调用viewGroup自己实现的dispatchDraw逻辑。)if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {dispatchDraw(canvas);drawAutofilledHighlight(canvas);if (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().draw(canvas);}if (isShowingLayoutBounds()) {debugDrawFocus(canvas);}} else {调用view的draw逻辑draw(canvas);}}} finally {结束绘制renderNode.endRecording();setDisplayListProperties(renderNode);}........return renderNode;}

上面提到的第二种情况:

第一种情况:不需要重新生成displayList(第一次不进入)
第二种情况:不是第一次,进入这里的时候cache失效为0,但是有list,但是不需要重新生成的情况;这种情况是不需要绘制自身,需要但是需要调用dispatchGetDisplayList去遍历child,为变化的child重新生成list,这是由于viewgroup某个child属性变化,导致父容器进行invalidate,此时只需要这个child重新执行ondraw;

ViewGroup.dispatchGetDisplayList()

/*** This method is used to cause children of this ViewGroup to restore or recreate their* display lists. It is called by getDisplayList() when the parent ViewGroup does not need* to recreate its own display list, which would happen if it went through the normal* draw/dispatchDraw mechanisms.** @hide*/
此方法用于使此 ViewGroup 的子级恢复或重新创建其显示列表。
当父级 ViewGroup 不需要重新创建其自己的显示列表时,
getDisplayList() 会调用此方法(
如果父级 ViewGroup 已通过正常的 draw/dispatchDraw 机制,则会发生这种情况)。@Override@UnsupportedAppUsageprotected 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);}}}

view.draw()

这里的注释相对清晰

  • 绘制遍历执行几个绘制步骤,这些步骤必须按照适当的顺序执行:
    1. 绘制背景
    1. 如有必要,保存画布的图层以准备淡入淡出
    1. 绘制视图的内容
    1. 绘制子项
    1. 如有必要,绘制淡入淡出边缘并恢复图层
    1. 绘制装饰(例如滚动条)
    1. 如有必要,绘制默认焦点突出显示

如果这个View需要重新绘制
layer是LAYER_TYPE_SOFTWARE,则使用一个普通的基于Bitmap的Canvas来传给View来绘制

硬件使用RenderNode.beginRecordings生成的RecordingCavas来绘制。

绘制的时候,存在很多步骤,会根据实际情况,先调用ondraw再dispatchDraw,对于viewGroup来说,调用dispatch就会调用到子view中,子view中则dispatch为空,则直接调用到了onDraw;

public void draw(@NonNull Canvas canvas) {final int privateFlags = mPrivateFlags;mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;/** Draw traversal performs several drawing steps which must be executed* in the appropriate order:**      1. Draw the background*      2. If necessary, save the canvas' layers to prepare for fading*      3. Draw view's content*      4. Draw children*      5. If necessary, draw the fading edges and restore layers*      6. Draw decorations (scrollbars for instance)*      7. If necessary, draw the default focus highlight*/// Step 1, draw the background, if neededint saveCount;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 contentonDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);drawAutofilledHighlight(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// Step 7, draw the default focus highlightdrawDefaultFocusHighlight(canvas);if (isShowingLayoutBounds()) {debugDrawFocus(canvas);}// we're done...return;}

阶段总结

Vsync接收->choreographer处理->ViewRootImpl.performTraversal->draw->ThreadRenderer.draw开始生产新帧内容
ViewRootImpl通过finishDrawing完成通知wms

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/65075.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gridcontrol表格某一列设置成复选框,选择多行(repositoryItemCheckEdit1)

1. 往表格中添加repositoryItemCheckEdit1 2. 事件&#xff1a; repositoryItemCheckEdit1.QueryCheckStateByValue repositoryItemCheckEdit1_QueryCheckStateByValue; private void repositoryItemCheckEdit1_QueryCheckStateByValue(object sender, DevExpress.XtraEditor…

重温设计模式--适配器模式

文章目录 适配器模式&#xff08;Adapter Pattern&#xff09;概述适配器模式UML图适配器模式的结构目标接口&#xff08;Target&#xff09;&#xff1a;适配器&#xff08;Adapter&#xff09;&#xff1a;被适配者&#xff08;Adaptee&#xff09;&#xff1a; 作用&#xf…

C语言项目 天天酷跑(上篇)

前言 这里讲述这个天天酷跑是怎么实现的&#xff0c;我会在天天酷跑的下篇添加源代码&#xff0c;这里会讲述天天酷跑这个项目是如何实现的每一个思路&#xff0c;都是作者自己学习于别人的代码而创作的项目和思路&#xff0c;这个代码和网上有些许不一样&#xff0c;因为掺杂了…

公交车信息管理系统:构建智能城市交通的基石

程序设计 本系统主要使用Java语言编码设计功能&#xff0c;MySQL数据库管控数据信息&#xff0c;SSM框架创建系统架构&#xff0c;通过这些关键技术对系统进行详细设计&#xff0c;设计和实现系统相关的功能模块。最后对系统进行测试&#xff0c;这一环节的结果&#xff0c;基本…

MDS-NPV/NPIV

在存储区域网络&#xff08;SAN&#xff09;中&#xff0c;域ID&#xff08;Domain ID&#xff09;是一个用于区分不同存储区域的关键参数。域ID允许SAN环境中的不同部分独立操作&#xff0c;从而提高效率和安全性。以下是关于域ID的一些关键信息&#xff1a; 域ID的作用&…

【网络安全产品大调研系列】1. 漏洞扫描

1. 为什么会出现漏扫技术&#xff1f; 每次黑客攻击事件进行追溯的时候&#xff0c;根据日志分析后&#xff0c;我们往往发现基本都是系统、Web、 弱口令、配置这四个方面中的其中一个出现的安全问题导致黑客可以轻松入侵的。 操作系统的版本滞后&#xff0c;没有更新补丁&am…

验证 Dijkstra 算法程序输出的奥秘

一、引言 Dijkstra 算法作为解决图中单源最短路径问题的经典算法,在网络路由、交通规划、资源分配等众多领域有着广泛应用。其通过不断选择距离源节点最近的未访问节点,逐步更新邻居节点的最短路径信息,以求得从源节点到其他所有节点的最短路径。在实际应用中,确保 Dijkst…

【论文阅读笔记】Learning to sample

Learning to sample 前沿引言方法问题声明S-NET匹配ProgressiveNet: sampling as ordering 实验分类检索重建 结论附录 前沿 这是一篇比较经典的基于深度学习的点云下采样方法 核心创新点&#xff1a; 首次提出了一种学习驱动的、任务特定的点云采样方法引入了两种采样网络&…

Mysql大数据量表分页查询性能优化

一、模拟场景 1、产品表t_product,数据量500万+ 2、未做任何优化前,cout查询时间大约4秒;LIMIT offset, count 时,offset 值较大时查询时间越久。 count查询 SELECT COUNT(*) AS total FROM t_product WHERE deleted = 0 AND tenant_id = 1 分页查询 SELECT * FROM t_…

pythonWeb~伍~初识Django

初识Django 1.技术栈 Python知识点&#xff1a;函数、面向对象。前端知识点&#xff1a;HTML、CSS、JavaScript、jQuery、BootStrap。MySQL数据库。Python的Web框架&#xff1a; Flask&#xff0c;自身短小精悍 第三方组件。Django&#xff0c;内部已集成了很多组件 第三方…

kimi搜索AI多线程批量生成txt原创文章软件-不需要账号及key

kimi搜索AI多线程批量生成txt原创文章软件介绍&#xff1a; 软件可以设置三种模型写文章&#xff1a;kimi&#xff1a;默认AI模型&#xff0c;kimi-search&#xff1a;联网检索模型 &#xff0c;kimi-research&#xff1a;探索版搜索聚合模型 1、可以设置写联网搜索文章&#…

DevNow x Notion

前言 Notion 应该是目前用户量比较大的一个在线笔记软件&#xff0c;它的文档系统也非常完善&#xff0c;支持多种文档格式&#xff0c;如 Markdown、富文本、表格、公式等。 早期我也用过一段时间&#xff0c;后来有点不习惯&#xff0c;就换到了 Obsidian &#xff0c;但是…

CSPM认证最推荐学习哪个级别?

一、什么是CSPM&#xff1f; CSPM的全称是Certified Strategic Project Manager&#xff0c;中文名称为“项目管理专业人员能力评价等级证书”。这是由中国标准化协会依据国家标准《项目管理专业人员能力评价要求》&#xff08;GB/T 41831-2022&#xff09;推出的一项认证&…

oracle: create new database

用database configuration Assistant 引导创建数据库。记得给system,sys 设置自己的口令&#xff0c;便于添加新操作用户。 创建操作用户&#xff1a; -- 别加双引号&#xff0c;否则&#xff0c;无法用 create user geovindu identified by 888888; create user geovin identi…

快速建站(网站如何在自己的电脑里跑起来) 详细步骤 一

1.选择开源网站 开源网站的建设平台有多种类型&#xff0c;每种类型都针对不同的需求和用途。我们根据自己的需求来选择 一、内容管理系统&#xff08;CMS&#xff09; 内容管理系统是开源网站建设中最常见的类型之一&#xff0c;它们提供了一个易于使用的界面&#xff0c;使用…

ECharts散点图-气泡图,附视频讲解与代码下载

引言&#xff1a; ECharts散点图是一种常见的数据可视化图表类型&#xff0c;它通过在二维坐标系或其它坐标系中绘制散乱的点来展示数据之间的关系。本文将详细介绍如何使用ECharts库实现一个散点图&#xff0c;包括图表效果预览、视频讲解及代码下载&#xff0c;让你轻松掌握…

【蓝桥杯——物联网设计与开发】拓展模块5 - 光敏/热释电模块

目录 一、光敏/热释电模块 &#xff08;1&#xff09;资源介绍 &#x1f505;原理图 &#x1f505;AS312 &#x1f319;简介 &#x1f319;特性 &#x1f505;LDR &#xff08;2&#xff09;STM32CubeMX 软件配置 &#xff08;3&#xff09;代码编写 &#xff08;4&#x…

Escalate_Linux靶机

Escalate_Linux靶机 前言&#xff1a;集合了多种liunx提权方法的靶场&#xff0c;通过该靶场可以简单的了解liunx提权方法 1&#xff0c;扫描一下端口 80/tcp open http 111/tcp open rpc 2049/udp nfs要知道对方的共享才能挂载 139/445 Samba SMB是一个协议名&#xff0c…

PPO算法基础(一)

PPO近端策略优化算法 我们今天还是主要来理解PPO算法的数学原理。PPO是一种策略梯度方法&#xff0c;简单的策略梯度对每个样本&#xff08;或者一组样本&#xff09;进行一次梯度更新&#xff0c;对单个样本执行多个梯度步骤会导致一些问题&#xff0c;因为梯度偏差太大&…

[机器学习]sklearn入门指南(1)

简介 scikit-learn&#xff08;简称sklearn&#xff09;是一个开源的Python机器学习库&#xff0c;它提供了简单而高效的工具用于数据挖掘和数据分析&#xff0c;并且拥有一个活跃的开发社区。它建立在NumPy、SciPy和matplotlib这些科学计算库之上&#xff0c;旨在提供一致且可…