Draw process of Android custom View

Keywords: Android html5

preface

The Draw process involves a lot of knowledge and is divided into three parts

The first part analyzes the relevant knowledge of hardware acceleration:
Draw ing process of Android custom View (in progress)
This article will deeply analyze hardware accelerated rendering and software rendering from the perspective of code.
Through this article, you will learn:

1. Software drawing process
2. Hardware accelerated rendering process
2. Influence of LayerType on painting
3. Where did Canvas come from
4. Drawing process family photo

Software drawing process

As mentioned in the previous article, in viewrootimpl - > Draw (XX), software rendering and hardware accelerated rendering go their separate ways:

The above figure is the entrance for Window to distinguish hardware accelerated rendering from software rendering.
From easy to difficult, let's take a look at the software drawing process.

drawSoftware(xx)

#ViewRootImpl.java
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                                 boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
        //Hold canvas
        final Canvas canvas;
        ...
        try {
            ...
            //Apply for a canvas object whose initial size is dirty
            canvas = mSurface.lockCanvas(dirty);
            //Set density
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            ...
        } catch (IllegalArgumentException e) {
            ...
            return false;
        } finally {
            ...
        }

        try {
            //Does the canvas need to be moved
            canvas.translate(-xoff, -yoff);
            //mView is the RootView added to the Window
            //For windows opened by Activity and Dialog, mView is known as DecorView
            //rootView draw() method
            mView.draw(canvas);
        } finally {
            try {
                //Submit the painted content to the Surface
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                ...
            }
        }
        return true;
    }

The key functions of the above methods are as follows:

1. Request a Canvas object from the Surface, which is of compatible Canvas type
2, after getting Canvas, call View.draw(Canvas) to start drawing RootView.
3. After the entire ViewTree is drawn, submit the content to the Surface

Note: RootView is only a pronoun, not the name of a View.
For some common rootviews, please move to: The source of Android input events (1)

For the View.draw(xx) method: Draw process of Android custom View (Part 1) It has been analyzed in detail. Combined with the above codes, it is shown in the figure below:

It can be seen that the software drawing has the following characteristics:

  • Recursively call the draw(xx) method of the sub layout from the RootView until each qualified View is drawn
  • During drawing, all views hold the same Canvas object

Introduce question 1: since all views have the same Canvas, how to determine the starting point and ending point of each View?
This problem will be analyzed later.

Hardware accelerated rendering process

outline

Software rendering is to write a series of Canvas operations into Bitmap. For hardware accelerated rendering, each View has a RenderNode. When rendering is needed, a RecordingCanvas is obtained from RenderNode. Like software rendering, it also calls a series of APIs of Canvas, However, these API calls are recorded as a series of operation behaviors and stored in the DisplayList. When a View recording ends, give the DisplayList to RenderNode. At this time, the drawing steps have been recorded in RenderNode. At this point, the hardware drawing for a single View is completed. This process is also known as the construction process of DisplayList.

Call procedure analysis

Let's take a look at the entry of hardware acceleration:

#ThreadedRenderer.java
    void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
        ...
        //(1) --- > recording operation
        //Update DisplayList of root View
        //From here, record the drawing operation in the DisplayList
        //The final record is in the rootRenderNode
        updateRootDisplayList(view, callbacks);

        //(2) --- > rendering
        //Render what you paint
        //Proxy is used as a bridge, and proxy is associated with rootRenderNode
        //Therefore, the drawing operation recorded in the previous step is finally handed over to a separate thread for rendering
        int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
        ...
    }

Focus on the recording process, and then analyze it:

#ThreadedRenderer.java
    private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
        //Traverse the ViewTree and build the DisplayList
        updateViewTreeDisplayList(view);

        //When the ViewTree DisplayList is built
        //At first, mRootNode did not have a DisplayList
        if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
            //Apply for Canvas
            RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
            try {
                ...
                //View. Updatedisdisplaylistifdirty() returns the renderNode associated with RootView
                //Now hang the RootView renderNode under the canvas, so that all rendernodes can be connected in series
                canvas.drawRenderNode(view.updateDisplayListIfDirty());
                ...
                mRootNodeNeedsUpdate = false;
            } finally {
                //Finally, hang the DisplayList under renderNode
                mRootNode.endRecording();
            }
        }
    }

    private void updateViewTreeDisplayList(View view) {
        //Mark that the View has been drawn
        view.mPrivateFlags |= View.PFLAG_DRAWN;
        //Mrecreatedisplaylist -- > indicates whether the View needs to rebuild the DisplayList, that is, re record. More frankly, whether the Draw process needs to be followed
        //If pflag is marked_ Invalid flag, that is, if the View needs to be refreshed, it needs to be rebuilt
        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                == View.PFLAG_INVALIDATED;
        //Clear the original value
        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
        //If necessary, update the DisplayList of the View
        view.updateDisplayListIfDirty();
        //The View has been rebuilt. No more rebuilding is needed
        view.mRecreateDisplayList = false;
    }

The above calls the method in View: updatedisisplaylistifdirty().
As the name suggests, if necessary, update the DisplayList of the View.

#View.java
    public RenderNode updateDisplayListIfDirty() {
        //When each View is constructed, a RenderNode:mRenderNode is created, which is called a render node
        final RenderNode renderNode = mRenderNode;
        //Whether hardware acceleration is supported is determined by judging View.AttachInfo.mThreadedRenderer
        if (!canHaveDisplayList()) {
            return renderNode;
        }

        //Remove the tag of the View
        //1. Rendering cache is invalid. 2. The rendering node has no DisplayList. 3. The rendering node has DisplayList, but it needs to be updated
        //If one of the three conditions is met, enter the condition code block
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                || !renderNode.hasDisplayList()
                || (mRecreateDisplayList)) {
            //If there is a DisplayList and the DisplayList does not need to be updated, it means that the View does not need to go through the Draw process again
            if (renderNode.hasDisplayList()
                    && !mRecreateDisplayList) {
                //Mark that the View has been drawn and the cache is valid
                mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                //Continue to see if the sub layout needs to build a DisplayList
                dispatchGetDisplayList(); //---------(1)

                return renderNode; // no work needed
            }
            
            //If the above conditions are not met, the View needs to build a DisplayList
            mRecreateDisplayList = true;
            //The coordinates of the View determined in the layout process are used at this time
            int width = mRight - mLeft;
            int height = mBottom - mTop;
            //Gets the currently set layerType
            int layerType = getLayerType();
            //Obtain the Canvas object from renderNode, and initialize the Canvas size to the View size
            //This Canvas is a RecordingCanvas type, which is simply understood as a Canvas used for recording
            final RecordingCanvas canvas = renderNode.beginRecording(width, height);

            try {
                //layerType has three values
                //In case of software drawing cache
                if (layerType == LAYER_TYPE_SOFTWARE) {
                    //---------(2)
                    //Then build the cache
                    buildDrawingCache(true);
                    //In fact, it is to write the drawing operation into the Bitmap
                    Bitmap cache = getDrawingCache(true);
                    if (cache != null) {
                        //Draw the Bitmap into Canvas
                        canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                    }
                } else {
                    //If the software drawing cache is not set
                    
                    //Generally used with Scroller sliding
                    computeScroll();
                    //Mscollx and mscolly are the rolling distance
                    //When mScrollX is positive, the canvas moves to the left and the drawn content moves to the left, which is the fundamental reason why the View content moves to the left when scroll is positive
                    canvas.translate(-mScrollX, -mScrollY);
                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;

                    //---------(3)
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        //The View does not need to draw its own content (including content, foreground, background, etc.)
                        //Directly initiate the request to draw the sub layout
                        dispatchDraw(canvas);
                        drawAutofilledHighlight(canvas);
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                        if (debugDraw()) {
                            debugDrawFocus(canvas);
                        }
                    } else {
                        //You need to draw yourself
                        draw(canvas);
                    }
                }
            } finally {
                //Finally, end the canvas recording and give the result of the recording: DisplayList to renderNode
                //---------(4)
                renderNode.endRecording();
                setDisplayListProperties(renderNode);
            }
        } else {
            //If the three conditions are not met, it is considered that the drawing has been completed
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        }
        //Return renderNode to the previous layer
        //---------(5)
        return renderNode;
    }

Five important points are listed in the notes to analyze one by one:
(1)
dispatchGetDisplayList()
This method is not implemented in View. It is implemented in ViewGroup as follows:

#ViewGroup.java
    protected void dispatchGetDisplayList() {
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            //Traversal sub layout
            final View child = children[i];
            if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
                //Rebuild sub layout DisplayList
                recreateChildDisplayList(child);
            }
        }
        ...
    }

    private void recreateChildDisplayList(View child) {
        //Determine whether reconstruction is needed
        child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
        child.mPrivateFlags &= ~PFLAG_INVALIDATED;
        //Call the sub layout reconstruction method
        child.updateDisplayListIfDirty();
        child.mRecreateDisplayList = false;
    }

It can be seen that the functions of dispatchGetDisplayList are as follows:

Traverse the sub layouts and call their reconstruction methods

In this way, start from the RootView and call updatedisisplaylistifdirty() recursively. If the sub layout needs to rebuild the DisplayList, re record the drawing operation. Otherwise, continue to find out whether the sub layout needs to rebuild the DisplayList.

(2)
buildDrawingCache(xx) is used to draw off screen cache, which will be described in detail later.

(3)
Skip drawing this paragraph for reference: Why didn't Android ViewGroup onDraw call

(4)
Hardware accelerated drawing is marked with start, recording and end:

1. renderNode generates Canvas for drawing – > beginrecoding. This is the beginning.
2. Call Canvas.drawXX() – > to record specific things. This is the recording process
3. renderNode finish drawing – > endrecording(), get the recording result: DisplayList from Canvas, and assign the result to renderNode, which is the end of recording

(5)
It can be seen from point 4 that the recorded results have been stored in the RenderNode, and the RenderNode needs to be returned. The RenderNode will be hung in the Canvas of the parent layout, that is, the Canvas of the parent layout already holds the DisplayList recorded by the child layout.

To simplify, use a diagram to show the hardware accelerated rendering process of a single View:

ViewTree hardware acceleration process:

Obviously, the hardware accelerated rendering process is the process of building the DisplayList. The DisplayList is built from the RootView recursive sub layout. When the whole DisplayList is built, it can be rendered, and the rendering thread is handed over to the GPU for processing, which greatly liberates the CPU.

Influence of LayerType on painting

The above describes the processes of software rendering and hardware accelerated rendering respectively. The starting point of analysis is whether the Window supports hardware acceleration and takes different branches.
From RootView to traversing all descendant views, they are either software drawn or hardware accelerated drawn. What will happen if the hardware acceleration of a View is disabled in the middle of hardware accelerated drawing? We mentioned earlier that hardware acceleration can be disabled by setting View - > LayerType. Next, we will analyze the impact of LayerType on the drawing process.
from Draw process of Android custom View (Part 1)
The analysis shows that no matter software rendering or hardware accelerated rendering, it will follow a common process:

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

This is also a recursive call process.
For a single View, where are the differences between software rendering and hardware acceleration?
The answer is: draw(x1,x2,x3) method

View soft and hard drawing bifurcation points

#View.java
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        //Does canvas support hardware acceleration
        //The default canvas does not support hardware acceleration
        //RecordingCanvas supports hardware acceleration
        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();

        //Whether to use RenderNode to draw, that is, whether the View supports hardware acceleration
        //The condition that the View supports hardware acceleration is: canvas supports hardware acceleration + the Window supports hardware acceleration
        boolean drawingWithRenderNode = mAttachInfo != null
                && mAttachInfo.mHardwareAccelerated
                && hardwareAcceleratedCanvas;
        //Animation related
        ...

        if (hardwareAcceleratedCanvas) {
            //canvas supports hardware acceleration. You need to check whether you need to rebuild the DisplayList
            mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
            mPrivateFlags &= ~PFLAG_INVALIDATED;
        }

        RenderNode renderNode = null;
        Bitmap cache = null;
        //Get LayerType. The default type of View is None
        int layerType = getLayerType();
        //------>(1)
        if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
            //1. Off screen software rendering cache is set. 2. View does not support hardware accelerated rendering
            //The two meet one
            if (layerType != LAYER_TYPE_NONE) {
                //Software cache or hardware cache may be set
                //At this time, the hardware cache is used as the software cache
                layerType = LAYER_TYPE_SOFTWARE;
                //Draw to software cache
                //------>(2)
                buildDrawingCache(true);
            }
            //Fetch software cache
            cache = getDrawingCache(true);
        }

        if (drawingWithRenderNode) { //----->(3)
            //The View supports hardware acceleration
            //Then try to build the DisplayList and return renderNode
            renderNode = updateDisplayListIfDirty();
            if (!renderNode.hasDisplayList()) {
                //I seldom go here
                renderNode = null;
                drawingWithRenderNode = false;
            }
        }

        int sx = 0;
        int sy = 0;
        if (!drawingWithRenderNode) {
            computeScroll();
            //Offset content when hardware acceleration is not used
            sx = mScrollX;
            sy = mScrollY;
        }

        //Pay attention to these two marks, which will be used below
        //1. There is software cache. 2. Hardware acceleration is not supported. If both are established at the same time, it indicates that software cache is used for drawing
        final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
        //1. There is no software cache. 2. Hardware acceleration is not supported. If both are established at the same time, it means that software rendering is used
        final boolean offsetForScroll = cache == null && !drawingWithRenderNode;

        if (offsetForScroll) {
            //------>(4)
            //For software drawing, move the canvas according to the View offset and content offset
            //The content scroll offset is included
            canvas.translate(mLeft - sx, mTop - sy);
        } else {
            if (!drawingWithRenderNode) {
                //------>(5)
                //If hardware acceleration is not supported, it may be software cache drawing
                //At this time, you also need to shift the canvas, but you don't need to consider the content scrolling offset
                canvas.translate(mLeft, mTop);
            }
            ...
        }

        ...

        if (!drawingWithRenderNode) {
            //Hardware acceleration is not supported
            if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
                //Reduce the canvas and limit the canvas display area, which is why the child layout display cannot exceed the parent layout area
                if (offsetForScroll) {
                    //If it is software drawing, the rolling distance is reduced
                    //------>(6)
                    canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
                } else {
                    //Otherwise, the rolling distance does not need to be considered
                    if (!scalingRequired || cache == null) {
                        canvas.clipRect(0, 0, getWidth(), getHeight());
                    } else {
                        canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                    }
                }
            }
            ...
        }

        if (!drawingWithDrawingCache) {
            //Draw without software cache
            if (drawingWithRenderNode) {
                //Support hardware acceleration
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                //Hang the renderNode of the View under the Canvas of the parent layout, and a connection is established here
                ((RecordingCanvas) canvas).drawRenderNode(renderNode);
            } else {
                //For software drawing, a drawing request is initiated: dispatchdraw (canvas) & draw (canvas);
                //------>(7)
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);
                }
            }
        } else if (cache != null) {
            //Software drawing cache exists
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
                ...
                //If the cache type is not set, the software drawing cache is written to the bitmap of canvas
                canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
            } else {
                ...
                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
            }
        }
        ...
        //The View has been built
        mRecreateDisplayList = false;
        return more;
    }

The judgment in this method is chaotic, and seven important points are extracted:
(1)
As long as off screen software cache is set or hardware acceleration is not supported, you need to use software cache to draw.

(3)
Hardware accelerated rendering is used as long as hardware acceleration is supported. Combination (1), do you feel a little contradictory? Consider one of the conditions in (1): off screen software cache is set and hardware acceleration is supported. According to the logic in (1), software cache rendering is enabled at this time. So (3) isn't it unnecessary to continue to accelerate rendering with hardware?
Review the snippet in updatedisisplaylistifdirty():

        if (layerType == LAYER_TYPE_SOFTWARE) {
            ...
            //Software cache drawing
            buildDrawingCache(true);
        } else {
            //Hardware Drawing
            ...
        }

Here the judge again.

(4)(5)
Canvas displacement
For software rendering, the Canvas is displaced, and the displacement distance considers the offset of View itself and the offset of View content.
For software cache rendering, the Canvas is displaced, and only the offset of the View itself is considered.
For hardware accelerated rendering, the displacement of Canvas is not seen.
In fact, for software cached rendering and hardware accelerated rendering, Canvas displacement includes both View itself offset and View content offset. It's just not in the above code.
For software cache drawing:

In builddrawingcacheimpl (XX) - > canvas.translate (- mscollx, - mscolly); Content offset.

For hardware accelerated rendering:

Offset the View itself in layout (XX) - > mrendernode.setlefttoprightbottom (mleft, MTop, mright, mbottom).
Update displaylistifdirty (XX) - > canvas. Translate (- mscollx, - mscolly); Content offset.

Therefore, regardless of software rendering / software cache rendering / hardware accelerated rendering, the three displace the Canvas, including the offset of View itself and the offset of content.

The above also explains question 1.

(6)
Canvas reduction
For software rendering, Canvas reduction includes View content offset.
For software cache drawing, Canvas is drawn into Bitmap.
For hardware accelerated rendering, trim in setdisplaylistproperties (XX) - > rendernode. Setcliptobounds (XX).
(7)
If it is software drawing, directly call dispatchDraw(xx)/draw(xx) to initiate drawing.

The draw(x1,x2,x3) method is used to determine the drawing method used by the View:

1. Hardware accelerated rendering
2. Software drawing
3. Software cache drawing

Software cache drawing

Let's see how to build a software cache:

#View.java
    public void buildDrawingCache(boolean autoScale) {
        //If the cache is marked invalid or empty
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
                mDrawingCache == null : mUnscaledDrawingCache == null)) {
            try {
                //Build cache
                buildDrawingCacheImpl(autoScale);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    }

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

        boolean clear = true;
        Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
        //If the bitmap does not exist or the size of the bitmap is inconsistent with that of the View, it is created
        ...
        Canvas canvas;
        if (attachInfo != null) {
            canvas = attachInfo.mCanvas;
            if (canvas == null) {
                //For the first time, there was no Canvas in AttachInfo
                canvas = new Canvas();
            }
            //Associated bitmap
            canvas.setBitmap(bitmap);
            attachInfo.mCanvas = null;
        } else {
            //I seldom go here
            canvas = new Canvas(bitmap);
        }
        computeScroll();
        final int restoreCount = canvas.save();
        //Scroll and PAN based on content
        canvas.translate(-mScrollX, -mScrollY);

        mPrivateFlags |= PFLAG_DRAWN;
        if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
                mLayerType != LAYER_TYPE_NONE) {
            //Mark to indicate that the software drawing cache is in effect
            mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
        }
        
        //Similarly, call the public method
        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchDraw(canvas);
            drawAutofilledHighlight(canvas);
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().draw(canvas);
            }
        } else {
            draw(canvas);
        }

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

        if (attachInfo != null) {
            //Record it and use it directly next time
            attachInfo.mCanvas = canvas;
        }
    }

In this way, the software cache is built, and the results are stored in Bitmap, which can be obtained through the following methods:

#View.java
    public Bitmap getDrawingCache(boolean autoScale) {
        //Disable software caching
        //Not prohibited by default
        if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
            return null;
        }
        if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
            //Whether the software cache drawing is enabled. It is not enabled by default
            //Build cache
            buildDrawingCache(autoScale);
        }
        //Return cache to
        return autoScale ? mDrawingCache : mUnscaledDrawingCache;
    }

This method can be used to get the page of the View.
Make a summary:

At the beginning, the hardware accelerated drawing process and the software drawing process go their own way without affecting each other.
1. When using software drawing, if the off screen cache type is set: software cache, the software drawing is invalid and only the software cache is used for drawing. If the hardware cache type is set, it is also drawn as software cache.
2. When using hardware accelerated rendering, if the off screen cache type is set: software cache, the hardware accelerated rendering fails and only the software cache is used for rendering. This is why setting software cache can disable hardware acceleration.
3. The drawing results of the software cache are saved in the bitmap, and the bitmap will eventually be drawn to the Canvas of the parent layout.

No matter which drawing type is used, the common calling method will be used: draw(xx)/dispatchDraw(xx).
Therefore, the drawing type is transparent for us to override onDraw(xx).

Where did Canvas come from

Software drawing
Start with viewrootimpl - > drawsoftware (XX) by:

canvas = mSurface.lockCanvas(dirty);

Canvas generated. The canvas is passed to all sub layouts through the View.draw(xx) method, so in this case, the entire ViewTree shares the same canvas object. The canvas type is CompatibleCanvas.
Hardware accelerated rendering
Start with view - > updatedisisplaylistifdirty (XX) by:

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

Canvas generated. It can be seen that canvas is regenerated for each View that supports hardware acceleration. The canvas type is RecordingCanvas.
Software cache drawing
Start with view - > builddrawingcacheimpl (XX) by:

canvas = new Canvas();

The Canvas is generated and recorded in AttachInfo. It will be used the next time the View software cache is built. It can be seen that for each View using software cache, a new Canvas is generated. Of course, if AttachInfo is available, it can be reused.
Canvas out of View
The above three have a common feature: the generated Canvas is finally connected with the Surface, so the content drawn through these canvases can finally be displayed on the screen.
Can I directly construct a Canvas that is separated from the View? The answer is yes.

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

As shown above, create a canvas and bitmap and associate them. Finally, call Canvas to draw API, and the result will be saved in Bitmap. This process is actually the method used by software cache rendering.
Of course, after we get the Bitmap, we want it to be easier to display. As long as it is associated with the View, it can be displayed on the screen. Associating to a View actually uses the Canvas associated with the View to draw the generated Bitmap on it,

Drawing process family photo

The drawing process is shown in the figure:

Simple software rendering and hardware accelerated rendering:

Drawing when software cache is set:

This concludes the Draw process series.
The source code of this article is based on Android 10.

Posted by cs-web on Wed, 06 Oct 2021 18:20:46 -0700