Based on Android API 26 Platform source code

Writing background

A Google search for "Android View Drawing" will give you a lot of information. 1. Measure -> layout -> draw process analysis. 2. Use Paint, Canvas, Drawable and Bitmap. 3. View/ViewGroup drawing sequence. 4. Measurement process of View. 5. Custom View should be overloadedCopy the code

Most of the articles are excellent and detailed.

But there is one problem that always bothers me: how to draw the View on the screen!!

So this article focuses on how to draw the View on the screen, other View drawing process we Google or refer to the Android View drawing process completely parsing, take you step by step in-depth understanding of View(2)

Customize a simple View

I should have started with the View source, but found that the view.java file has about 26488 lines of source code. I don’t know how much hair it takes to chew through a code this long. We have to find another way.

Let’s take a look at some code

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Paint mPaint = new Paint(); mPaint.setColor(0xffff0000); mPaint.setStrokeWidth(10); setContentView(new View(this) { @Override protected void onDraw(Canvas canvas) { canvas.drawLine(0, 0, getWidth(), getHeight(), mPaint); }}); }}Copy the code

The code is relatively simple. We draw a red line from the top left to the bottom right of the screen.

1. Place the drawn code in the onDraw() method 2. Create a Paint and set the color brush width. DrawLine (0, 0, getWidth(), getHeight(), mPaint).Copy the code

The above explanation seems extremely natural, but! What seems to be wrong ????

Where does Canvas come from??

If we look at the View source, we’ll answer: draw(Canvas Canvas) from the parent class.

But! Who calls the parent class draw(Canvas Canvas)?

That’s when a lot of people

All right, let’s move on to the next stage.

Trace the onDraw() call stack

To get a call to the onDraw() method, we make our first attempt.

AndroidStudio Find Usages

AndroidStudio’s Find lead is very powerful and will quickly help us Find where to call onDraw().

But! We got 77 results.

Seventy-seven isn’t that many, but it’s a lot of work. So: this road is blocked

Sacrifice almighty debug

Static analysis was blocked, and it felt like only a single step would quickly help us find the most important results out of 77.

For single-step debugging, we need to do the following

1. Download Android source code. If not, there will be a prompt in the upper right corner of AndroidStudio when you click to View the View source code. Click download. 2. Prepare the VM. The Android version of the VM is the same as the project compileSdkVersion. 3. Make a breakpoint in our custom View's onDraw() method 4. Select Debug AppCopy the code

The following figure shows the breakpoint information, with the method call stack at the breakpoint on the right. Due to the limited screen, the stack information is not completely truncated.

We can trace the corresponding source code by clicking the method on the right. Since the method stack is too long, we choose to start from the bottom of the stack.

Analyze the onDraw() call stack

The first part
  at android.os.Looper.loop(Looper.java:164)
  at android.app.ActivityThread.main(ActivityThread.java:6541)
  at java.lang.reflect.Method.invoke(Method.java:-1)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Copy the code

Zygote is the parent of all Android processes and is used to incubate Android processes. To learn more, Google or check out what Zygote does when it starts up.

ActivityThread is our Android process, and the ActivityThread main() method is the entry point for our entire Android application. The main() method blocks the thread by calling the looper.loop () method, which starts the entire Android application (if it doesn’t block, the main() method ends the process). This thread is also known as the UI thread in Android, and the Looper in this case is the MainLooper.

To sum up, the code from this section is just the Android process startup process. But it doesn’t have much to do with drawing a View.

The second part
  at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1386)
  at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6733)
  at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
  at android.view.Choreographer.doCallbacks(Choreographer.java:723)
  at android.view.Choreographer.doFrame(Choreographer.java:658)
  at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
  at android.os.Handler.handleCallback(Handler.java:789)
  at android.os.Handler.dispatchMessage(Handler.java:98)
Copy the code

Received and dealt with a message handler FrameDisplayEventReceiver. The run (), said we have received a message from a map page. Choreographer is the unified scheduling interface drawing mechanism added since Android4.1.

In addition, we found that the method call stack went into the ViewRootImpl object. At this point we need to understand the view wrootimPL. If you don’t know viewrotimpl, you can first explore the Android Window mechanism to understand the relationship between View, Viewrotimpl and Window. Or look at the following two simple summaries

1. All views in Android are drawn on Windows. 2. Each application window is created with a corresponding ViewRootImpl object. ViewRootImpl is the root node responsible for communication between View and Windows Manager Live.Copy the code

Summary part 2: when a page draw message is received, call the viewrootimpl.dotraversal () method.

Part III (Key Points)
  at android.view.View.updateDisplayListIfDirty(View.java:18073)
  at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:643)
  at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:649)
  at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:757)
  at android.view.ViewRootImpl.draw(ViewRootImpl.java:2980)
  at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2794)
  at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2347)
Copy the code

There’s a new object called ThreadedRenderer, which we can guess from its name is related to rendering pages. A series of calls through the ThreadedRenderer

draw()  -> updateRootDisplayList()  -> updateViewTreeDisplayList() 
Copy the code

Will call to View. The updateDisplayListIfDirty ()

public RenderNode updateDisplayListIfDirty() { final RenderNode renderNode = mRenderNode; ... if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || ! RenderNode. IsValid () | | (mRecreateDisplayList)) {... int width = mRight - mLeft; int height = mBottom - mTop; int layerType = getLayerType(); final DisplayListCanvas canvas = renderNode.start(width, height); canvas.setHighContrastText(mAttachInfo.mHighContrastText); Try {... } finally { renderNode.end(canvas); setDisplayListProperties(renderNode); } } else { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; } return renderNode; }Copy the code

Note here

final DisplayListCanvas canvas = renderNode.start(width, height);
Copy the code

Oh my god!! We finally see the canvas assignment code.

Rendernode. start(width, height)

public DisplayListCanvas start(int width, int height) {
    return DisplayListCanvas.obtain(this, width, height);
}
Copy the code

Found that the obtain() method was called

static DisplayListCanvas obtain(@NonNull RenderNode node, int width, int height) {
    if (node == null) throw new IllegalArgumentException("node cannot be null");
    DisplayListCanvas canvas = sPool.acquire();
    if (canvas == null) {
        canvas = new DisplayListCanvas(node, width, height);
    } else {
        nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
                width, height);
    }
    canvas.mNode = node;
    canvas.mWidth = width;
    canvas.mHeight = height;
    return canvas;
}
Copy the code

Then we found ourselves in the JNI space.

private DisplayListCanvas(@NonNull RenderNode node, int width, int height) {
    super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
    mDensity = 0; // disable bitmap density scaling
}

@CriticalNative
private static native long nCreateDisplayListCanvas(long node, int width, int height);
@CriticalNative
private static native void nResetDisplayListCanvas(long canvas, long node,
        int width, int height);
Copy the code

This is the end of our tracking when we meet JNi, but we already know where Canvas was created. As for the bottom stuff, keep track of it when you can.

The fourth part

After the analysis of the previous three parts, the fourth part is relatively easy. It’s easy to see that it’s a circular call. It just corresponds to the View drawing rule: draw the parent View first and then draw the child View.

Here’s another question: why did it take 6 layers of nesting to get to our top View?

Here we can use AndroidStudio -> Tools -> Android -> Layout Inspector just found 6 layers of nesting

Note that the Activity’s ContentView contains only one simple View and already has six layers of nesting

A few words at the end

This is just to introduce the Canvas assignment process during the Android View drawing process. Using the Canvas Api calls we can draw various pages on the screen.

However, this is only a small step in the Android drawing process. This step can even be ignored if you don’t want to trace the source of the Canvas.

For the rest, you can read the source code yourself, or read other people’s blogs. If you have any questions, please leave a message.

reference

Android View drawing process fully analyzed, take you step by step in-depth understanding of View(2)

What does the Zygote process do when it starts?