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