文章

07.draw绘制

07.draw绘制

draw 绘制流程

绘制相关概念

  1. 一个 View 有一个 RenderNode,RenderNode 通过 RecordingCanvas 记录了 displaylist,还包括一些属性 (scale/translation)(一个 drawXXX 操作都会记录在 displaylist 中)
  2. 根 View 包含了所有子 View 的 RenderNode
  3. 绘制根 RenderNode 就可以绘制整颗 View 树的 UI 了

DisplayList

  • 什么是 DisplayList?
    DisplayList 是一个基本绘制元素,包含元素原始的属性(位置、尺寸、角度、透明度等)和对应的 Canvas 的 drawXXX() 方法。

一个 View 对应一个 DisplayList,每一个 View 的 drawXXX 操作,都记录到 DisplayList 中;在 Android4.1 及以上版本,DisplayList 支持属性,如果 View 的属性发生变化(比如 Scala,Apla,Translate)只需把属性更新给 GPU,不需要生成新的 DisplayList。

  • 和软件绘制直接绘制相比?

Display List 是以视图为单位进行构建的,因此每一个视图都对应有一个 Display List。这里说的 Display List 与 Open GL 里面的 Display List 在概念上是类似的,不过是两个不同的实现。DisplayList 的本质是一个缓冲区,它里面记录了即将要执行的绘制命令序列。这些绘制命令最终会转化为 Open GL 命令由 GPU 执行。这意味着我们在调用 Canvas API 绘制 UI 时,实际上只是将 Canvas API 调用及其参数记录在 Display List 中,然后等到下一个 Vsync 信号到来时,记录在 Display List 里面的绘制命令才会转化为 Open GL 命令由 GPU 执行。

与直接执行绘制命令相比,先将绘制命令记录在 DisplayList 中然后再执行有两个好处。第一个好处是在绘制窗口的下一帧时,若某一个视图的 UI 没有发生变化,那么就不必执行与它相关的 Canvas API,即不用执行它的成员函数 onDraw,而是直接复用上次构建的 Display List 即可。第二个好处是在绘制窗口的下一帧时,若某一个视图的 UI 发生了变化,但是只是一些简单属性发生了变化,例如位置和透明度等简单属性,那么也不必重建它的 Display List,而是直接修改上次构建的 Display List 的相关属性即可,这样也可以省去执行它的成员函数 onDraw。


Android 应用程序窗口视图是树形结构的,因此它们的 Display List 是从根视图开始构建的,并且子视图的 Display List 包含在父视图的 Display List 中。这意味着根视图的 DisplayList 包含了 Android 应用程序窗口 UI 所有的绘制命令,因此最后我们只需要对根视图的 Display List 进行渲染即可得到 Android 应用程序窗口的 UI

cbnis

RenderNode/RecordingCanvas

一个 RenderNode 包含若干个 DisplayList,通常一个 RenderNode 对应一个 View,包含 View 自身(及 View 一些属性,如 scale、translation 等)及其子 View 的所有 DisplayList。

RenderNode 通过 RecordingCanvas 来记录 displaylist,这个 RecordingCanvas 不能用于软件绘制

1
2
3
4
5
6
7
8
9
RenderNode renderNode = new RenderNode("myRenderNode"); 
renderNode.setLeftTopRightBottom(0, 0, 50, 50); // Set the size to 50x50
RecordingCanvas canvas = renderNode.beginRecording(); // 开始记录
try {
    // Draw with the canvas
    canvas.drawRect(...);
} finally {
    renderNode.endRecording(); // 结束记录
}
  1. RenderNode.beginRecording(width, height) 记录当前 RenderNode 的 displaylist
1
2
3
4
5
6
7
8
public @NonNull RecordingCanvas beginRecording(int width, int height) {
    if (mCurrentRecordingCanvas != null) {
        throw new IllegalStateException(
                "Recording currently in progress - missing #endRecording() call?");
    }
    mCurrentRecordingCanvas = RecordingCanvas.obtain(this, width, height);
    return mCurrentRecordingCanvas;
}
  1. RenderNode.endRecording 结束记录,如果 display list valid,那么 hasDisplayList() 会返回 true
1
2
3
4
5
6
7
8
9
10
11
public void endRecording() {
    if (mCurrentRecordingCanvas == null) {
        throw new IllegalStateException(
                "No recording in progress, forgot to call #beginRecording()?");
    }
    RecordingCanvas canvas = mCurrentRecordingCanvas;
    mCurrentRecordingCanvas = null;
    long displayList = canvas.finishRecording();
    nSetDisplayList(mNativeRenderNode, displayList);
    canvas.recycle();
}
  1. hasDisplayList() RenderNode 是否还有 display list
1
2
3
public boolean hasDisplayList() {
    return nIsValid(mNativeRenderNode);
}
  1. discardDisplayList() 释放资源,清空 display list
1
2
3
public void discardDisplayList() {
    nSetDisplayList(mNativeRenderNode, 0);
}

RecordingCanvas

和 RenderNode 配合使用

  • drawRenderNode(RenderNode renderNode) 将 displaylist 记录到 canvas

ViewRootImpl#performDraw() FrameWrok 层

ViewRootImpl 充当的是 View 和 window 之间的纽带。在 startActivity 之后,经过与 ActivityManagerService 的 IPC 交互,会在 ActivityThread 的 handleResumeActivity 方法中执行到 getWindow().addView,就是将根布局 Decor 添加到 window 中以显示。getWindow 会以 WindowManagerGloble 来执行 addView 方法,其中就会创建 ViewRootImpl 实例并调用其 setView 方法传入 Decor 布局,在 setView 中会执行到 performTranvesals 方法

1
2
3
4
5
6
7
8
9
10
// Activity Android29
// 在Activity的makeVisible调用WindowManager.addView
void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

WindowManager 的实现类是 WindowManagerImpl,最终调用的是 WindowManagerGlobal 的 addView,WindowManagerGlobal 是个单例

1
2
3
4
5
6
// WindowManagerImpl#addView
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

WindowManagerGlobal 的 addView,创建 ViewRootImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ViewRootImpl root;
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    // do this last because it fires off messages to start doing things
    try {
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
            removeViewLocked(index, true);
        }
        throw e;
    }
}

最终调用 ViewRootImpl 的 setView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// ViewRootImpl Android29
View mView; // 如果是Activity,那么是DecorView

// 在WindowManagerGlobal#addView初始化ViewRootImpl并调用setView传递view进来,
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
             // If the application owns the surface, don't enable hardware acceleration
            if (mSurfaceHolder == null) {
                // While this is supposed to enable only, it can effectively disable
                // the acceleration too.
                enableHardwareAcceleration(attrs); // 根据设置开启硬件加速
            }
            requestLayout();
        }
    }
}
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

// scheduleTraversals()通过编舞者,调用doTraversal(),最终执行到performTraversals()
private void performTraversals() {
    // ...
    performDraw()
    // ...
}
private void performDraw() {
    // ...
    boolean canUseAsync = draw(fullRedrawNeeded);
    // ... 
}

ViewRootImpl#setView → ViewRootImpl#requestLayout → ViewRootImpl#doTraversal() → ViewRootImpl#performTraversals,最后调用到 ViewRootImpl#performDraw(),下面我们看看 performDraw(),又调用了 draw()

在 ViewRootImpl#draw 中,根据硬件加速是否可用,分为非硬件加速和硬件加速

  1. 硬件加速可用,调用 mAttachInfo.mThreadedRenderer#draw(),而我们知道 mAttachInfo.mThreadedRenderer 就是 ThreadedRenderer
  2. 硬件加速不可用,调用 drawSoftware()
1
2
3
4
5
6
7
8
9
10
11
12
// ViewRootImpl Android29
private boolean draw(boolean fullRedrawNeeded) {
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { // 开启了硬件加速
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
        } else { // 未开启硬件加速
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {
                return false;
            } 
        }
    }
}

大概从 Android4.+ 开始,默认情况下都是支持跟开启了硬件加速的。

draw 绘制流程(硬件加速和软件)

draw 流程绘制总结

Android 的绘制流程图

sracc

硬件绘制和软件绘制区别

渲染场景纯软件绘制硬件加速加速效果分析
页面初始化绘制所有 View创建所有 DisplayListGPU 分担了复杂计算任务
在一个复杂页面调用背景透明 TextView 的 setText(),且调用后其尺寸位置不变重绘脏区所有 ViewTextView 及每一级父 View 重建 DisplayList重叠的兄弟节点不需 CPU 重绘,GPU 会自行处理
TextView 逐帧播放 Alpha / Translation / Scale 动画每帧都要重绘脏区所有 View除第一帧同场景 2,之后每帧只更新 TextView 对应 RenderNode 的属性 (Android10,都不会重绘了?)刷新一帧性能极大提高,动画流畅度提高
修改 TextView 透明度重绘脏区所有 View直接调用 RenderNode.setAlpha() 更新加速前需全页面遍历,并重绘很多 View;加速后只触发 DecorView.updateDisplayListIfDirty,不再往下遍历,CPU 执行时间可忽略不计
软件绘制

应用程序调用 invalidate() 更新 UI 的某一部分,失效 (invalidation) 消息将会在整个视图层中传递,计算每个需要重绘的区域(即脏区域)。然后 Android 系统将会重绘所有和脏区域有交集的 view

硬件绘制

Android 系统仍然使用 invalidate() 和 draw() 来绘制 view,但在处理绘制上有所不同。Android 系统记录绘制命令到显示列表,而不是立即执行绘制命令。另一个优化就是 Android 系统只需记录和更新标记为脏(通过 invalidate())的 view

非硬件加速(软件绘制)drawSoftware

drawSoftware 其实很简单,利用 Surface.lockCanvas,向 SurfaceFlinger 申请一块匿名共享内存内存分配,同时获取一个普通的 SkiaCanvas,用于调用 Skia 库,进行图形绘制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ViewRootImpl Android29
public final Surface mSurface = new Surface();
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    // Draw with software renderer.
    final Canvas canvas;
    
    //(锁定界面中需要绘制的部分)每个窗口都关联一个Surface,当这个窗口需要绘制
    //UI 时,就会调用关联的 Surface 的
    //lockCanvas()方法获得一个Canvas,(这个Canvas 封装了由 Skia 提供的 2D 
    //图形绘制接口)并且向 SurfaceFlinger Dequeue 一个Graphic 
    //Buffer,绘制的内容都会输出到 Graphic Buffer 上再交由 SurfaceFlinger 
    //对图形内容的合成及显示到屏幕上。
    canvas = mSurface.lockCanvas(dirty);
    
    // 将View的内容绘制到Canvas上
    mView.draw(canvas); // 如果是Activity,这个mView是DecorView,DecorView是FrameLayout,最终调用的是View#draw
    
    // 通知SurfaceFlinger进行图层合成
    // 绘制完成之后,调用unlockCanvasAndPost请求将Canvas 
    // 显示到屏幕上,其本质上是向SurfaceFlinger服务Queue一个Graphic Buffer。
    surface.unlockCanvasAndPost(canvas);
}

默认情况下 Skia 的绘制没有采用 GPU 渲染的方式(虽然 Skia 也能用 GPU 渲染),也就说默认 drawSoftware 工作完全由 CPU 来完成,不会牵扯到 GPU 的操作,但是 8.0 之后,Google 逐渐加重了 Skia,开始让 Skia 接手 OpenGL,间接统一调用,将来还可能是 Skia 同 Vulkan 的结合

硬件加速 ThreadedRenderer#draw

硬件加速绘制包括两个阶段:构建阶段 + 绘制阶段

构建阶段(CPU)

构建阶段就是递归遍历所有视图,将需要的操作缓存下来,之后再交给单独的 Render 线程利用 OpenGL 渲染。

在 Android 硬件加速框架中,View 视图被抽象成 RenderNode 节点,View 中的绘制都会被抽象成一个个 DrawOp(DisplayListOp),比如 View 中 drawLine,构建中就会被抽象成一个 DrawLintOp,drawBitmap 操作会被抽象成 DrawBitmapOp,每个子 View 的绘制被抽象成 DrawRenderNodeOp,每个 DrawOp 有对应的 OpenGL 绘制命令,同时内部也握着绘图所需要的数据。
02d34

View 的构造方法中,会构造出一个 RenderNode

ThreadedRenderer/HardwareRenderer

HardwareRenderer 是整个硬件加速绘制的入口,实现是一个 ThreadedRenderer 对象。

  1. 在 UI 线程中完成 DrawOp 集构建,在 ViewRootImpl#enableHardwareAcceleration 创建
  2. 负责跟渲染线程通信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected RenderNode mRootNode;
public HardwareRenderer() {
    // 用来标识整个DrawOp树的根节点,有个这个根节点就可以访问所有的绘制Op 
    mRootNode = RenderNode.adopt(nCreateRootRenderNode());
    mRootNode.setClipToBounds(false);
    
    // 用来跟渲染线程进行通信的句柄
    mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
    if (mNativeProxy == 0) {
        throw new OutOfMemoryError("Unable to create hardware renderer");
    }
    Cleaner.create(this, new DestroyContextRunnable(mNativeProxy));
    ProcessInitializer.sInstance.init(mNativeProxy);
}
构建 View 树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// ThreadedRender Android29 
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
    choreographer.mFrameInfo.markDrawStart();
    // 构建View的DrawOp树
    updateRootDisplayList(view, callbacks);
    // ... 
    // 通知RenderThread线程绘制
    int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
    if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
        setEnabled(false);
        attachInfo.mViewRootImpl.mSurface.release();
        // Invalidate since we failed to draw. This should fetch a Surface
        // if it is still needed or do nothing if we are no longer drawing
        attachInfo.mViewRootImpl.invalidate();
    }
    if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
        attachInfo.mViewRootImpl.invalidate();
    }
}
// ThreadedRender Android29 
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    updateViewTreeDisplayList(view); // 1
    // ...
    if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) { // mRootNote,HardwareRenderer创建的
        // 获取DisplayListCanvas
        RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight); // 2
        try {
            // 利用canvas缓存Op // 3
            final int saveCount = canvas.save();
            canvas.translate(mInsetLeft, mInsetTop);
            callbacks.onPreDraw(canvas);

            canvas.enableZ();
            canvas.drawRenderNode(view.updateDisplayListIfDirty()); // 4
            canvas.disableZ();

            callbacks.onPostDraw(canvas);
            canvas.restoreToCount(saveCount);
            mRootNodeNeedsUpdate = false;
        } finally {
            // 5 将所有Op填充到RootRenderNode
            mRootNode.endRecording(); 
        }
    }
}
// ThreadedRender Android29 
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#updateDisplayListIfDirty构建op树
    view.mRecreateDisplayList = false;
}
  1. updateRootDisplayList,构建 RootDisplayList,其实就是构建 View 的 DrawOp 树;调用 updateViewTreeDisplayList,进而调用根 View 的 updateDisplayListIfDirty,遍历调用 View.updateDisplayListIfDirty 来构建 DrawnOp 并缓存至 view 的 RenderNode。
  2. 通过 mRootNode 获取一个 DisplayListCanvas
  3. 利用 DisplayListCanvas 构建并缓存所有的 DrawOp
  4. 将根 View 的缓存 DrawOp 设置到 RootRenderNode 中
  5. 将 DisplayListCanvas 缓存的 DrawOp 填充到 RenderNode

现在看下 View 递归构建 DrawOp

View#updateDisplayListIfDirty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// View Android29
/**
 * Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
 * @hide
 */
public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    if (!canHaveDisplayList()) { // View没有attached 或者 没有mThreadedRenderer(mThreadedRenderer=null)
        // can't populate RenderNode, don't try
        return renderNode;
    }
    
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                || !renderNode.hasDisplayList()
                || (mRecreateDisplayList)) {
        // 如果不需要重新创建displayList,直接将构建事件分发下去,由于第一次是一定会构建的,所以此时renderNode已经和子view绑定。
        if (renderNode.hasDisplayList()
                && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            //  分发构建事件
            dispatchGetDisplayList();
            //  返回更新后的renderNode
            return renderNode; // no work needed
        }

        // 到了这一步说明需要重新构建DisplayList
        mRecreateDisplayList = true;
            
        // 获取一个 DisplayListCanvas 用于绘制 硬件加速 ; 记录view tree的RenderNode,该canvas会一直往下分发    
        final RecordingCanvas canvas = renderNode.beginRecording(width, height);
    
        try {
            // 硬件加速里面的软件图层绘制,通过getDrawingCache获得的Bitmap会被记录到父View的 DisplayList中。
            // 而当Parent View的DisplayList的命令被执行时,记录在里面的Bitmap再通过Open GL命令来绘制。
            if (layerType == LAYER_TYPE_SOFTWARE) {
                buildDrawingCache(true);
                Bitmap cache = getDrawingCache(true);
                if (cache != null) {
                    canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                }
            } else {
                // 如果仅仅是ViewGroup,并且自身不用绘制,直接递归子View
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    dispatchDraw(canvas);
                } else {
                    // 调用自己draw,如果是ViewGroup会递归子View
                    draw(canvas);
                }
                // ...
            }
        } finally {
            // 缓存构建Op
            renderNode.end(canvas);
            setDisplayListProperties(renderNode);
        }
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}


// View#draw(Canvas canvas, ViewGroup parent, long drawingTime)
if (hardwareAcceleratedCanvas) {
    // Clear INVALIDATED flag to allow invalidation to occur during rendering, but
    // retain the flag's value temporarily in the mRecreateDisplayList flag
    mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
    mPrivateFlags &= ~PFLAG_INVALIDATED;
}
// 一旦当前view需要重新构建RenderNode(即设置了PFLAG_INVALIDATED),构建分发流程直接中断,直接进入绘制流程(即dispatchDraw和draw流程)

绘制阶段 (GPU)

构建完成后,就可以将这个绘图 Op 树交给 Render 线程进行绘制,这里是同软件绘制很不同的地方,软件绘制时,View 一般都在主线程中完成绘制,而硬件加速,除非特殊要求,一般都是在单独线程中完成绘制,如此以来就分担了主线程很多压力,提高了 UI 线程的响应速度。
nbuw7
DrawOp 树构建完毕后,UI 线程利用 RenderProxy 向 RenderThread 线程发送一个 DrawFrameTask 任务请求,RenderThread 被唤醒,开始渲染;然后再通过共享内存将 Graphic Buffer 分享至 SurfaceFlinger 进行合成和显示。

1
2
3
4
5
// HardwareRenderer Android29
@SyncAndDrawResult
public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
    return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
}

View#draw(Canvas) 应用层 draw

一个完整的绘制过程会依次绘制以下几个内容:

1
2
3
4
5
背景
主体(onDraw())
子 View(dispatchDraw())
滑动边缘渐变和滑动条
前景(前景的支持是在 Android 6.0(也就是 API 23)才加入的;之前其实也有,不过只支持 FrameLayout,而直到 6.0 才把这个支持放进了 View 类里。)

一般来说,一个 View(或 ViewGroup)的绘制不会这几项全都包含,但必然逃不出这几项,并且一定会严格遵守这个顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// View Android29
public void draw(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 // 1. 绘制背景
     *      2. If necessary, save the canvas' layers to prepare for fading // 2. 如果需要,save Canvas layer
     *      3. Draw view's content // 3. 绘制View,draw自身内容
     *      4. Draw children // 4. 绘制子View
     *      5. If necessary, draw the fading edges and restore layers // 5. 如果需要restore canvas layer,和2成对存在
     *      6. Draw decorations (scrollbars for instance) // 6. 绘制装饰(scrollbar)、前景等
     */
     
    // Step 1, draw the background, if needed
    int saveCount;

    drawBackground(canvas); // 绘制背景
    
    // skip step 2 & 5 if possible (common case) // 一般情况下会跳过步骤2和5
    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); // 绘制子view内容

        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 (debugDraw()) {
            debugDrawFocus(canvas);
        }
        // we're done...
        return;
    }
    
    // ... 
    // Step 3, draw the content
    onDraw(canvas); // 绘制自身内容

    // Step 4, draw the children
    dispatchDraw(canvas); // 绘制 children 
    
    // Step 5, draw the fade effect and restore layers
    // ...
    
    canvas.restoreToCount(saveCount);
    
    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas); // 绘制装饰 (前景色,滚动条)
 }

drawBackground 绘制背景

背景,它的绘制发生在一个叫 drawBackground() 的方法里,但这个方法是 private 的,不能重写,你如果要设置背景,只能用自带的 API 去设置(xml 布局文件的 android:background 属性以及 Java 代码的 View.setBackgroundXxx() 方法,而不能自定义绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// View Android29
private Drawable mBackground;
public void setBackgroundDrawable(Drawable background) {
    mBackground = background;
}
private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;
    if (background == null) { //如果没有设置背景,就不进行绘制
        return;
    }

    //如果设置了背景吗,且背景的大小发生了改变,
    //就用 layout 计算出的四个边界值来确定背景的边界
    setBackgroundBounds();

    // Attempt to use a display list if requested.
    if (canvas.isHardwareAccelerated() && mAttachInfo != null
            && mAttachInfo.mThreadedRenderer != null) {
        mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);

        final RenderNode renderNode = mBackgroundRenderNode;
        if (renderNode != null && renderNode.hasDisplayList()) {
            setBackgroundRenderNodeProperties(renderNode);
            ((RecordingCanvas) canvas).drawRenderNode(renderNode);
            return;
        }
    } 

    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    if ((scrollX | scrollY) == 0) {
        background.draw(canvas);  // 调用 Drawable 的 draw 方法来进行背景的绘制
    } else {
        canvas.translate(scrollX, scrollY);
        background.draw(canvas); // 调用 Drawable 的 draw 方法来进行背景的绘制
        canvas.translate(-scrollX, -scrollY);
    }
}

mBackground 是通过代码 setBackground 设置的,或 xml 的 android:background 属性配置的:

1
2
3
4
5
6
7
8
9
10
// View Android29
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    case com.android.internal.R.styleable.View_background:
        background = a.getDrawable(attr);
        break;
    // ...
    if (background != null) {
        setBackground(background);
    }
}

onDraw  绘制自身内容

默认空实现

onDrawForeground 绘制前景(包括滚动条,)在 View 内容之上

滑动边缘渐变和滑动条以及前景,这两部分被合在一起放在了 onDrawForeground() 方法里,这个方法是可以重写的。

滑动边缘渐变和滑动条可以通过 xml 的 android:scrollbarXXX 系列属性或 Java 代码的 View.setXXXScrollbarXXX() 系列方法来设置;前景可以通过 xml 的 android:foreground 属性或 Java 代码的 View.setForeground() 方法来设置。而重写 onDrawForeground() 方法,并在它的 super.onDrawForeground() 方法的上面或下面插入绘制代码,则可以控制绘制内容和滑动边缘渐变、滑动条以及前景的遮盖关系。

onDrawForeground() 这个方法是 API 23 才引入的,所以在重写这个方法的时候要确认你的 minSdk 达到了 23,不然低版本的手机装上你的软件会没有效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// View Android29
public void onDrawForeground(Canvas canvas) {
    onDrawScrollIndicators(canvas); // 绘制指示器
    onDrawScrollBars(canvas); // 绘制滚动条

    final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
    if (foreground != null) {
        if (mForegroundInfo.mBoundsChanged) {
            mForegroundInfo.mBoundsChanged = false;
            final Rect selfBounds = mForegroundInfo.mSelfBounds;
            final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

            if (mForegroundInfo.mInsidePadding) {
                selfBounds.set(0, 0, getWidth(), getHeight());
            } else {
                selfBounds.set(getPaddingLeft(), getPaddingTop(),
                        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
            }

            final int ld = getLayoutDirection();
            Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                    foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
            foreground.setBounds(overlayBounds);
        }
        // 调用 Drawable 的 draw 方法,绘制前景色
        foreground.draw(canvas);
    }
}

dispatchDraw 绘制子 view 内容

ViewGroup 本身是继承 View 的,它的基本绘制流程也是通过父类 View 进行的,只不过它重写了 dispatchDraw 方法,来进行子元素的绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ViewGroup Android29
protected void dispatchDraw(Canvas canvas) {
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;

    int clipSaveCount = 0;
    final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
    if (clipToPadding) {
        clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
        canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                mScrollX + mRight - mLeft - mPaddingRight,
                mScrollY + mBottom - mTop - mPaddingBottom);
    }
    
    for (int i = 0; i < childrenCount; i++) {
        // ...
        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);
        }
    }
    // ...
    if (clipToPadding) {
        canvas.restoreToCount(clipSaveCount);
    }
    // ...
}
// ViewGroup Android29
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

View#drawChild

drawChild 又调用了 View#draw(Canvas, ViewGroup, Long)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// ViewGroup Android29
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
    boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas;
    Bitmap cache = null;
    int layerType = getLayerType()
    if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
         if (layerType != LAYER_TYPE_NONE) { 
             // 未开启硬件加速,调用 View 的 buildDrawingCache 方法
             buildDrawingCache(true);
        }
        cache = getDrawingCache(true);
    }
    if (drawingWithRenderNode) { // 开启了硬件加速
        // Delay getting the display list until animation-driven alpha values are
        // set up and possibly passed on to the view
        renderNode = updateDisplayListIfDirty(); // 调用 View 的 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) { // setWillNotDraw(true) 
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchDraw(canvas);
            } else {
                draw(canvas);
            }
        }
    }
}

View#buildDrawingCache 未开启硬件加速

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// View Android29
public void buildDrawingCache(boolean autoScale) {
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
            mDrawingCache == null : mUnscaledDrawingCache == null)) {
        // ...
        buildDrawingCacheImpl(autoScale);
    }
}
// View Android29
private void buildDrawingCacheImpl(boolean autoScale) {
    // Fast path for layouts with no backgrounds
    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { // 如果不需要进行自身绘制,就直接调用 dispatchDraw 绘制子 Children
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        dispatchDraw(canvas);
        drawAutofilledHighlight(canvas);
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().draw(canvas);
        }
    } else {
        draw(canvas); // 否则就直接调用 View 的 draw 方法
    }
}

View#updateDisplayListIfDirty 开启了硬件加速

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// View Android29
public RenderNode updateDisplayListIfDirty() {
    // ...
    if (layerType == LAYER_TYPE_SOFTWARE) {
        buildDrawingCache(true);
        Bitmap cache = getDrawingCache(true);
        if (cache != null) {
            canvas.drawBitmap(cache, 0, 0, mLayerPaint);
        }
    } else {
        computeScroll();

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

        // Fast path for layouts with no backgrounds
        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
            dispatchDraw(canvas);
            drawAutofilledHighlight(canvas);
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().draw(canvas);
            }
            if (debugDraw()) {
                debugDrawFocus(canvas);
            }
        } else {
            draw(canvas);
        }
    }
}

setWillNotDraw  WILL_NOT_DRAW

View 默认为 false;ViewGroup 默认为 true

1
2
3
4
5
6
// ViewGroup
private void initViewGroup() {
    // ViewGroup doesn't draw by default
    if (!debugDraw()) {
        setFlags(WILL_NOT_DRAW, DRAW_MASK);
}

1
2
3
4
5
6
7
// ViewGroup Android29
public boolean willNotDraw() {
    return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW;
}
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

在 setFlags 中根据 WILL_NOT_DRAW 标记位和是否设置了 background 或 foreground 来添加 PFLAG_SKIP_DRAW 标记位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// View#setFlags Android29
void setFlags(int flags, int mask) {
    if ((changed & DRAW_MASK) != 0) {
        if ((mViewFlags & WILL_NOT_DRAW) != 0) { // 设置了WILL_NOT_DRAW标记位即setWillNotDraw(true)
            if (mBackground != null
                    || mDefaultFocusHighlight != null
                    || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) { // background不为null或foreground不为null
                mPrivateFlags &= ~PFLAG_SKIP_DRAW; // 清除PFLAG_SKIP_DRAW标记位
            } else {
                mPrivateFlags |= PFLAG_SKIP_DRAW; // 添加PFLAG_SKIP_DRAW标记位
            }
        } else {
            mPrivateFlags &= ~PFLAG_SKIP_DRAW; // 清除PFLAG_SKIP_DRAW标记位
        }
        requestLayout();
        invalidate(true);
    }
}

View#draw(Canvas, ViewGroup, Long) 中会根据 PFLAG_SKIP_DRAW 来执行 dispathDraw(Canvas) 还是 draw(Canvas)

Ref

draw 总结

小结

  1. ViewGroup 默认会绕过 draw() 方法,默认设置了 PFLAG_SKIP_DRAW 的 flag,换而直接执行 dispatchDraw();如果你自定义了某个 ViewGroup 的子类(比如 LinearLayout)并且需要在它的除 dispatchDraw() 以外的任何一个绘制方法内绘制内容,你可能会需要调用 View.setWillNotDraw(false) 这行代码来切换到完整的绘制流程(是「可能」而不是「必须」的原因是,有些 ViewGroup 是已经调用过 setWillNotDraw(false) 了的,例如 ScrollView
  2. ViewGroup 设置了 setWillNotDraw(false),但是没有 background 和 foreground,是否还会 draw()?

不会调用 draw 了

  1. ViewGroup 设置了 background 或 foreground 会调用 draw,会取消 PFLAG_SKIP_DRAWflag,就会调用 draw;但如果设置了 background 或 foreground 后设置 setWillNotDraw(true),那么不会 draw 了
  2. 重写 draw,onDraw,dispatchDraw,onDrawForeground,代码写在 super 前面还是后面,影响效果的覆盖
  3. 有的时候,一段绘制代码写在不同的绘制方法中效果是一样的,这时你可以选一个自己喜欢或者习惯的绘制方法来重写。但有一个例外:如果绘制代码既可以写在 onDraw() 里,也可以写在其他绘制方法里,那么优先写在 onDraw() ,因为 Android 有相关的优化,可以在不需要重绘的时候自动跳过 onDraw() 的重复执行,以提升开发效率。享受这种优化的只有 onDraw() 一个方法

draw 流程

draw绘制流程 章节

onDraw 绘制自身

super.onDraw() 前 or 后?

  1. 自定义 View 的,都可以,super.onDraw() 就是个空实现
  2. 你绘制的内容需要盖住控件原有的内容,先调用 super.onDraw()
  3. 你绘制的内容被控件原有的内容盖住,后调用 super.onDraw()

写在 super.onDraw() 的下面

把绘制代码写在 super.onDraw() 的下面,由于绘制代码会在原有内容绘制结束之后才执行,所以绘制内容就会盖住控件原来的内容。
这是最为常见的情况:为控件增加点缀性内容。比如,在 Debug 模式下绘制出 ImageView 的图像尺寸信息:

1
2
3
4
5
6
7
8
9
10
public class AppImageView extends ImageView {
    // ...
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (DEBUG) {
            // 在 debug 模式下绘制出 drawable 的尺寸信息
            ...
        }
    }
}

udakj

写在 super.onDraw() 的上面

如果把绘制代码写在 super.onDraw() 的上面,由于绘制代码会执行在原有内容的绘制之前,所以绘制的内容会被控件的原内容盖住。
比如你可以通过在文字的下层绘制纯色矩形来作为「强调色」:

1
2
3
4
5
6
7
8
public class AppTextView extends TextView {
    // ...
    protected void onDraw(Canvas canvas) {
       // ... 
       // 在 super.onDraw() 绘制文字之前,先绘制出被强调的文字的背景
        super.onDraw(canvas);
    }
}

7z8sh

dispatchDraw 绘制子 View 的方法

Android 的绘制顺序:在绘制过程中,每一个 ViewGroup 会先调用自己的 onDraw() 来绘制完自己的主体之后再去通过 dispatchDraw() 绘制它的子 View。

  1. onDraw() 只是负责自身主体内容绘制的
  2. dispatchDraw 绘制子 View

虽然 View 和 ViewGroup 都有 dispatchDraw() 方法,不过由于 View 是没有子 View 的,所以一般来说 dispatchDraw() 这个方法只对 ViewGroup(以及它的子类)有意义。

本文由作者按照 CC BY 4.0 进行授权