Android Render(3) Drawing process analysis under hardware acceleration of Android 7.1 source code

The analysis starts with two methods: draw(Canvas canvas) and draw(Canvas canvas, ViewGroup parent, long drawingTime):

draw(Canvas canvas) and draw(Canvas canvas, ViewGroup parent, long drawingTime) methods seem to accept different parameters from the surface. In fact, the second phase has a sequential, subordinate relationship, but it also depends on whether the current View is a top-level DecorView, that is, if a View has a parent view, the two methods of View are called differently.Ordered, of course, only DecorView is the top-level View, DecorView has no parent view.

Normally, the draw(Canvas canvas, ViewGroup parent, long drawingTime) method is called by the parentView of the current view. In the draw(Canvas canvas, ViewGroup parent, long drawingTime) method, depending on whether hardware acceleration is supported or not, the process that does not work will eventually call the draw(Canvas canvas) method to do the drawing in progress, and the draw(Canvas canvas) method will call the dispThe atchDraw (canvas) method, which distributes the drawing downward, calls draw(Canvas canvas, ViewGroup parent, long drawingTime) in the dispatchDraw(canvas) method.Drawing passes down layers.

However, as a top-level DecorView, ViewRootImpl calls DecorView's draw(Canvas canvas) method to directly open the entire view tree's drawing, and DecorView's draw(Canvas canvas) method calls dispatchDraw(canvas) method to begin distributing draws downward.One layer passes to the bottom of the view tree.

The actual drawing of the entire View Hierarchy begins with the DecorView draw(Canvas canvas) method. The following describes the process from which the Activity is started to invoke the DecorView draw(Canvas canvas) method:

/**1*/ ApplicationThread Of onTransact Method Received SystemServer Progressive SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION start-up Activity Of Binder information
                ↓
/**2*/ ApplicationThread.scheduleLaunchActivity() ///**3*/ ActivityThread.scheduleLaunchActivity() //Schedule Startup Activity/**4*/ ActivityThread.handleLaunchActivity()  //Processing Startup Activity/**5*/ ActivityThread.handleResumeActivity() // Activity's Reume associates DecorView with ViewRootImpl/**6*/ WindowManagerGlobal.addView() //Save window information globally/**7*/ ViewRootImpl.setView()  //Associate DecorView with ViewRootImpl and draw the interface/**8*/ ViewRootImpl.requestLayout() //Request to draw ViewTree/**9*/ ViewRootImpl.scheduleTraversals() // Arrange traversal /**10*/ ViewRootImpl.doTraversal() ///**11*/ ViewRootImpl.performTraversals() //Executing traversal calls relayoutWindow performMeasure performLayout performDraw and other methods as appropriate, which are closely related to drawing/**12*/ ViewRootImpl.performDraw() //Execute Drawing/**13*/ ViewRootImpl.draw(boolean fullRedrawNeeded)//Differentiate whether hardware acceleration is supported for different drawing processes                
                ↓
             .........
                ↓
/**14*/ DecorView.draw(Canvas canvas)  //Hardware acceleration will be set here whether you walk or not

The whole interface is drawn at the touch of DecorView.draw(Canvas canvas) method!

1. draw(canvas,parent,drawingTime) and draw(canvas) work differently

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
  /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     * This method is called by the parent control ViewGroup.drawChild()
     * drawChild()The method is again called by the dispatchDraw(Canvas canvas) method in ViewGroup
     * This is where the View specializes rendering behavior based on layer type,
     * and hardware acceleration.
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
        /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
         *
         * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
         * HW accelerated, it can't handle drawing RenderNodes.
         */
         //Determine if the current View supports hardware accelerated drawing
        boolean drawingWithRenderNode = mAttachInfo != null
                && mAttachInfo.mHardwareAccelerated
                && hardwareAcceleratedCanvas;

        ......slightly

        //Drawing Nodes Used for Hardware Accelerated Drawing
        RenderNode renderNode = null;
        //Drawing cache used by cpu drawing
        Bitmap cache = null;
        //Gets the drawing type LAYER_TYPE_NONE LAYER_TYPE_SOFTWARE LAYER_TYPE_HARDWARE of the current View
        int layerType = getLayerType(); // TODO: signify cache state with just 'cache' local
        if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {  //If it is a cpu drawing type
             if (layerType != LAYER_TYPE_NONE) {
                 // If not drawing with RenderNode, treat HW layers as SW
                 layerType = LAYER_TYPE_SOFTWARE;
                 //Start cpu drawing cache build
                 buildDrawingCache(true);
            }
            //Get cpu drawing cache results stored in Bitmap
            cache = getDrawingCache(true);
        }

        if (drawingWithRenderNode) { //Supports hardware acceleration
            // Delay getting the display list until animation-driven alpha values are
            // set up and possibly passed on to the view
            //Update gpu drawing list saved in RenderNode
            renderNode = updateDisplayListIfDirty();
            if (!renderNode.isValid()) {
                // 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;
                //gpu drawing failure identification
                drawingWithRenderNode = false;
            }
        }

        ......slightly

        //cpu drawing succeeded and gpu drawing failed
        final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;

        ......slightly


        if (!drawingWithDrawingCache) { //Go gpu to draw
            if (drawingWithRenderNode) { //Support for gpu drawing
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                //DisplayList collected by gpu drawing
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            } else { //Go gpu drawing and suddenly do not support gpu drawing (possibly in extreme cases)
                // Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    //No content does not need to draw itself, just distribute Draw Sub View directly down
                    dispatchDraw(canvas);
                } else {
                    //Draw yourself before distributing Draw SubView
                    draw(canvas);
                }
            }
        } else if (cache != null) { //Walk cpu drawing and cpu drawing cache is not null

         ......slightly

            //Bitmap, which stores the cpu drawing cache, uses canvas to move cpu drawing (skia rendering engine)
            canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);

        }
         ......slightly

        return more;
    }

    /**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    @CallSuper
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        int saveCount;
        // Step 1 Draws Background
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        //Step 2, save the canvas layer to prepare for fading if necessary
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, Draw View's own content
            if (!dirtyOpaque) onDraw(canvas);

            //Step 4, Draw the contents of the subView
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            //Step 5, if necessary, draw the faded edges and restore the layer by getOverlay().add(drawable); add a picture or something
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (columns such as scrollbars)
            onDrawForeground(canvas);

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

        ......slightly
}

You can see that the updateDisplayListIfDirty() method in View is the key to gpu drawing and the buildDrawingCache() method is the key to cpu drawing.
Both the updateDisplayListIfDirty() and buildDrawingCache() methods call the draw(canvas) method of View, but the draw(canvas) parameter passed to the updateDisplayListIfDirty() method makes it HWUI capable.In the buildDrawingCache() method, the draw(canvas) method is passed in as ordinary Canvas.

It is also clear to us that for our developers, the draw(Canvas canvas, ViewGroup parent, long drawingTime) method is a drawing entry for the View, from which we decide whether to go cpu or gpu.

The draw(Canvas canvas) method is a specific drawing task. If the drawing is accelerated by gpu hardware, it is drawn using the DisplayList Canvas canvas, which saves the drawing DisplayList in the drawing node RenderNode.If it's CPU soft drawing, use the normal Canvas canvas drawing, save the drawing cache in a Bitmap, and finally use the skia rendering engine CPU to draw the data in the cached Bitmap using the canvas.drawBitmap() method.

2. Top DecorView hardware accelerated call draw(canvas)

Be careful:
DecorView is actually a FrameLayout,FrameLayout is a ViewGroup,ViewGroup is an abstract class that inherits View, and the draw(canvas) method is only implemented in the View class, so the call to DecorView's draw(canvas) actually ends up calling the View's draw(canvas) method.

As mentioned above, DecorView is a top-level View and its draw(canvas) method is the beginning of drawing. How does ViewRootImpl call DecorView's draw(canvas) under hardware acceleration?

Start with the draw(boolean fullRedrawNeeded) method of ViewRootImpl:

/***********************************************************************        
   /**1*/ ApplicationThread Of onTransact Method Received SystemServer Progressive SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION start-up Activity Of Binder information
                ↓
   /**2*/ ApplicationThread.scheduleLaunchActivity() ///**3*/ ActivityThread.scheduleLaunchActivity() //Schedule Startup Activity/**4*/ ActivityThread.handleLaunchActivity()  //Processing Startup Activity/**5*/ ActivityThread.handleResumeActivity() // Activity's Reume associates DecorView with ViewRootImpl/**6*/ WindowManagerGlobal.addView() //Save window information globally/**7*/ ViewRootImpl.setView()  //Associate DecorView with ViewRootImpl and draw the interface/**8*/ ViewRootImpl.requestLayout() //Request to draw ViewTree/**9*/ ViewRootImpl.scheduleTraversals() // Arrange traversal /**10*/ ViewRootImpl.doTraversal() ///**11*/ ViewRootImpl.performTraversals() //Executing traversal calls relayoutWindow performMeasure performLayout performDraw and other methods as appropriate, which are closely related to drawing/**12*/ ViewRootImpl.performDraw() //Execute Drawing/**13*/ Differentiate whether hardware acceleration is supported for different drawing processes*********************************/  

   private void draw(boolean fullRedrawNeeded) {

      ......slightly

        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { //Supports hardware acceleration and requires drawing
            if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {

                ......slightly
                //1 Key to hardware accelerated decoorView draw(canvas) method
                mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
            } else {

                ......slightly

                //2 Key to non-hardware accelerated decoorView draw(canvas) method
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }
               ......slightly
    }
}

ThreadedRenderer is a new RenderThread thread on 5.0 for each process, a rendering thread that ensures smooth animation execution even when the main thread is blocked.Is a processing thread for asynchronous drawing.
For more information see:
http://www.jianshu.com/p/bc1c1d2fadd1
http://blog.csdn.net/guoqifa29/article/details/45131099

Let's start by analyzing the hardware acceleration call to DecorView's draw(canvas) method and look at the flow inside mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this):

/**
 *5.0 New Rendering Threads
 */
public final class ThreadedRenderer {
    ......slightly 
    /**
     * Draws the specified view.
     *
     * @param view The view to draw.
     * @param attachInfo AttachInfo tied to the specified view.
     * @param callbacks Callbacks invoked when drawing happens.
     */
    void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {

        //1 Circle the end of the exam to update `DecorView``DisplayList`
        updateRootDisplayList(view, callbacks);

    }

    //It's actually updating the `DisplayList'of `DecorView`
    private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {

        //Update `DisplayList'of `DecorView`
        updateViewTreeDisplayList(view);

        if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
            //1 Get a DisplayListCanvas canvas
            DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
            try {
                final int saveCount = canvas.save();
                canvas.translate(mInsetLeft, mInsetTop);
                callbacks.onHardwarePreDraw(canvas);

                canvas.insertReorderBarrier();

                //2 Draw the enderNode of the obtained DecorView
                //view.updateDisplayListIfDirty() is actually the updateDisplayListIfDirty method that is called by DicrView.
                //Calling the updateDisplayListIfDirty method through the layers will eventually get the entire view tree's drawing node`RenderNode`

                canvas.drawRenderNode(view.updateDisplayListIfDirty());

                canvas.insertInorderBarrier();

                callbacks.onHardwarePostDraw(canvas);
                canvas.restoreToCount(saveCount);
                mRootNodeNeedsUpdate = false;
            } finally {
                //3 Recycle resources after the entire View tree has been drawn
                mRootNode.end(canvas);
            }
        }
    }
  ......slightly
}

From above, you can see that updateViewTree DisplayList (view) method is invoked by updating the DislayList of DecorView. See:

/**
 *5.0 New Rendering Threads
 */
public final class ThreadedRenderer {
    ......slightly
    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;
    //It's also a call to view.update DisplayList IfDirty() method to update the DecorView's DislayList
        view.updateDisplayListIfDirty();
        view.mRecreateDisplayList = false;
    }
    ......slightly
}

As you can see here, the updateDisplayListIfDirty method of DecorView is the key to hardware acceleration, and it is also the draw(canvas) method called to DecorView from here to open drawing. See the source code:

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {

   ......slightly

    /**
     * Update a View's Drawing DisplayList saved in RenderNode Return 
     * The returned RenderNode is drawn by the drawRenderNode(RenderNode) method of the ThreadedRenderer thread
     * Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
     * @hide
     */
    @NonNull
    public RenderNode updateDisplayListIfDirty() {
        final RenderNode renderNode = mRenderNode;

               ......slightly

            //Get a DisplayListCanvas from renderNode
            final DisplayListCanvas canvas = renderNode.start(width, height);
            canvas.setHighContrastText(mAttachInfo.mHighContrastText);

            try {
                if (layerType == LAYER_TYPE_SOFTWARE) { //draw(canvas) method for CPU
                    buildDrawingCache(true); //Creating a CPU drawing cache will call View's
                    Bitmap cache = getDrawingCache(true);  //Save CPU Drawing Cache
                    if (cache != null) {
                        canvas.drawBitmap(cache, 0, 0, mLayerPaint); //Ska draws collected Bitmap cache data
                    }
                } else { //Accelerate GPU drawing for hardware
                    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); //View itself does not require drawing to be distributed directly to child View drawing
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                    } else {
                        //Draw using DisplayListCanvas, the required drawing will be saved in DisplayList
                        draw(canvas);
                    }
                }
            } finally {
                renderNode.end(canvas);  //Recycling resources
                setDisplayListProperties(renderNode);
            }
        } else {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        }
        return renderNode; //Returns renderNode, the drawing node holding the GPU drawing data DisplayList
    }

   ......slightly

}

You can see that in the updateDisplayListIfDirty method of View, the draw(canvas) method of View is called directly after RenderNode and DisplayListCanvas are prepared with hardware acceleration support.

Summary process:

//Prerequisite is to support hardware acceleration
ViewRootImpl.draw(boolean fullRedrawNeeded) 
                   ↓
ThreadedRenderer.draw(view,attachInfo,hardwareDrawCallbacks)                   
                   ↓           
ThreadedRenderer.updateRootDisplayList(view, callbacks)
                   ↓
DecorView.updateDisplayListIfDirty()
                   ↓  
        DecorView.draw(canvas)

3. Top DecorView non-hardware accelerated call draw(canvas)

As you can see from the drawing (boolean fullRedrawNeeded) method of ViewRootImpl above, if it is a CPU drawing, the drawSoftware() method will be used.So let's see how drawSoftware() calls to DecorView's draw(canvas) method:

ViewRootImpl```drawSoftware() method:

    /**
     * @return true if drawing was successful, false if an error occurred
     */
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {

        // Draw with software renderer.
        final Canvas canvas;

            ......slightly

            //Get a common Canvas from Surface
            canvas = mSurface.lockCanvas(dirty);

            ......slightly

            //Call DecorView's draw(canvas) method
            mView.draw(canvas);

            ......slightly

        return true;
    }

Summary process:

//If hardware acceleration is not supported
ViewRootImpl.draw(boolean fullRedrawNeeded) 
                   ↓
ViewRootImpl.drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)                  
                   ↓  
        DecorView.draw(canvas)

Summary of draw(canvas) method calls for DecorView:

DecorView exists as a top-level View, and its drawing is determined by ViewRootImpl whether it is drawn by CPU or GPU, then the DecorView's draw(canvas) method is invoked to open the whole interface.

The remaining Views have their own parent control that calls the draw(canvas,parent,drawingTime) method, determines whether the current View is CPU or GPU drawing, and then calls draw(canvas) in the draw(canvas,parent,drawingTime) method.

4. Hardware accelerated draw(canvas, parent, drawingTime) calls draw(Canvas canvas)

As I mentioned above, the whole interface is drawn from DecorView's draw(canvas) method, and normal View's draw(canvas, parent, drawingTime).
View's draw(canvas, parent, drawingTime), draw(Canvas canvas) and updateDisplayListIfDirty() methods I will not paste here, there are already, give the flow directly:

//GPU Drawing
Vew.draw(Canvas canvas, ViewGroup parent, long drawingTime)
     ↓
Vew.updateDisplayListIfDirty()
     ↓
Vew.draw(displayListCanvas)

5. Non-hardware accelerated draw(canvas, parent, drawingTime) calls draw(Canvas canvas)

Give the CPU drawing process first:

//CPU Drawing
Vew.draw(Canvas canvas, ViewGroup parent, long drawingTime)
     ↓
Vew.buildDrawingCache(boolean autoScale)
     ↓
Vew.buildDrawingCacheImpl(boolean autoScale)
     ↓ 
Vew.draw(displayListCanvas)

The draw(canvas, parent, drawingTime) method calls to buildDrawingCache as you can see in the code above. Here's a look at the buildDrawingCache and buildDrawingCacheImpl methods:

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {

   ......slightly

       public void buildDrawingCache(boolean autoScale) {
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
                mDrawingCache == null : mUnscaledDrawingCache == null)) {
            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                        "buildDrawingCache/SW Layer for " + getClass().getSimpleName());
            }
            try {
                //Perform CPU Drawing Cache Creation
                buildDrawingCacheImpl(autoScale);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    }

       ......slightly

    /**
     * private, internal implementation of buildDrawingCache, used to enable tracing
     */
    private void buildDrawingCacheImpl(boolean autoScale) {

           ......slightly

            //Bitmap to save CPU drawing cache
            Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;

           ......slightly
        if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {

            ......slightly

            try {
                //Re-create and assign if cache Bitmap is empty
                bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
                        width, height, quality);
                bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
                if (autoScale) {
                    mDrawingCache = bitmap;
                } else {
                    mUnscaledDrawingCache = bitmap;
                }
                if (opaque && use32BitCache) bitmap.setHasAlpha(false);
            } catch (OutOfMemoryError e) {

                ......slightly
            }

            clear = drawingCacheBackgroundColor != 0;
        }

        Canvas canvas;
        if (attachInfo != null) { //Processing Canvas
            canvas = attachInfo.mCanvas;
            if (canvas == null) {
                canvas = new Canvas();
            }
            //canvas's Bitmap is set to the cache Bitmap we created
            canvas.setBitmap(bitmap);

            ......slightly

        } else {
            // This case should hopefully never or seldom happen
            canvas = new Canvas(bitmap);
        }
            ......slightly

        // Fast path for layouts with no backgrounds
        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;

            dispatchDraw(canvas); //I don't need to draw myself, just distribute sub View drawings

            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().draw(canvas);
            }
        } else {
            //You need to draw your own, then distribute sub View drawings, all of which will be drawn on the cache Bitmap
            draw(canvas);
        }

            ......slightly
    }

   ......slightly

   }

Summary:

Whether hardware acceleration is supported or not, the draw(canvas) method of View is invoked.
DisplayList Canvas canvas with hardware acceleration only. The resulting DisplayList data is stored in RenderNode, the drawing node of each View, and handed over to the DraRenderNode (renderNode) method of DisplayListCanvas to handle the rendering operation.
Without hardware acceleration, all drawing cache data is saved in a cached Bitmap and the data is handed over to skia for rendering by Canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint).

DisplayList build analysis see: http://www.jianshu.com/p/7bf306c09c7e

Keywords: Android

Added by Qense on Sun, 19 May 2019 01:32:38 +0300