background

For Android development, in an interview, you are often asked, what is the drawing process of a View? I also often ask interviewees about the drawing process of a View.

For more than 3 years developers know onMeasure/onLayout/ontouch basic, know what they do is to do, it is enough?

If you come to our company, I will be your interviewer. Maybe I will check what you have done in the past three years. What do you know about View and ask more detailed questions, such as onMeasure of LinearLayout and onLayout process? When were they initiated and in what order?

If you know all the above questions, maybe you will come to our company (if you need to extrapolate, you can contact me, Android/IOS positions are needed). Maybe I will investigate where your canvas of Draw comes from and how it is created and displayed on the screen. How much depth do you have?

As the mobile development market becomes more mature and saturated, many companies with enough people need senior programmers. In saying that we all know, the interview to build aircraft cannon, go in after the screw, for a 3 years or more than 5 years of Android development do not understand a little bit of Android deep things, not very good mix. Pulled so many useless things, or back to today’s main topic, Android drawing principle analysis.

This paper introduces ideas

Start with a few easy questions from the interview questions, layer by layer, down to the principles of screen drawing.

Before talking about the principle of Android drawing, first introduce the basic working principle of Android View, this article does not introduce the event transfer process.

View drawing works

Let’s start with a few important categories that are often asked in interviews

The relationship between Activity,Window(PhoneWindow), and DecorView

To understand their relationship, let’s look at the code directly, starting with the setContentView where the Activity starts.

//Activity
 /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    
     public Window getWindow(a) {
        return mWindow;
    }
Copy the code

It calls getWindow’s setContentView, which I’ll talk about next, but when was this mWindow created?

//Activity
private Window mWindow;

final void attach(Context Context, ActivityThread aThread,···) {attachBaseContext(Context);
        mFragments.attachHost(null /*parent*/);
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
}
Copy the code

In attach to the Activity, a PhoneWindow is created, which is the Window implementation class.

So let’s continue with the setContentView

//PhoneWindow
  @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else{ mLayoutInflater.inflate(layoutResID, mContentParent); }}Copy the code

In the setContentView, if mContentParent is empty, installDecor is called and infalte is laid out to mContentParent. Check out the installDecor

//PhoneWindow
 // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
  ViewGroup mContentParent;
  
  private DecorView mDecor;
  
  private void installDecor(a) {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) { mContentParent = generateLayout(mDecor); }}protected DecorView generateDecor(int featureId) {
        return new DecorView(context, featureId, this, getAttributes());
    }
Copy the code

In installDecor, a DecorView is created. Look at the mContentParent comment and we can see that mDecor itself is mDecor or the contents part of mDecor.

So in summary, we have some idea of the relationship between the three,

  • The Activity includes a PhoneWindow,
  • PhoneWindow inherits from Windows
  • The Activity sets the View to the PhoneWindow with setContentView
  • The PhoneWindow contains a DecorView to which the final layout is added.

Understand ViewRootImpl WindowManager, WindowManagerService relationship between (WMS)

After looking at the relationship, we know that the layout is finally added to the DecorView. So how does the DecorView get added to the Framework layer of the system?

When the Activity is ready, it will eventually call makeVisible in the Activity and add a View through WindowManager, as shown below

//Activity 
 void makeVisible(a) {
        if(! mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded =true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }
Copy the code

So what is their relationship? (As mentioned below, client server is the concept of client server in Binder communications.)

The following are the key points to understand

  • ViewRootImpl (client) : hold the View with WMS link mAttachInfo, mAttachInfo hold ViewRootImpl. ViewRootImpl is ViewRoot implementation, WMS management window, need to inform the client for a certain operation, Such as event response. The ViewRootImpl has an inner class W that extends from iWindow.stub, which is actually a Binder for interacting with WMS IPC. ViewRootHandler is also an internal class inheriting Handler for making asynchronous calls to data returned from remote IPC.

  • WindowManger(client): The client needs to create a window, and the specific task of creating the window is done by WMS,WindowManger is like a department manager, who has any requirements to tell it, it interacts with WMS, the client cannot directly interact with WMS.

  • WindowManagerService(WMS) : Responsible for window creation, display, etc.

Redraw the View

From the above relationship, the view wrootimPL is used to receive messages from THE WMS. So let’s take a look at some of the View drawing code in ViewRootImpl.

Here again, view arotimpl has two important inner classes

  • Class W inherits Binder to receive messages from WMS
  • The ViewRootHandler class inherits the Handler to receive asynchronous messages from class W

Let’s take a look at the ViewRootHandler class (View setVisible for example).

// ViewRootHandler(ViewRootImpl inner class for asynchronous message processing, similar to Acitivity startup)

// The first step is that the Handler receives the message from W (Binder)
@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        case MSG_INVALIDATE:
             ((View) msg.obj).invalidate();
            break;
        case MSG_INVALIDATE_RECT:
            final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
            info.target.invalidate(info.left, info.top, info.right, info.bottom);
            info.recycle();
            break;
        case MSG_DISPATCH_APP_VISIBILITY:/ / handle the VisiblehandleAppVisibility(msg.arg1 ! =0);
            break; }}void handleAppVisibility(boolean visible) {
        if(mAppVisible ! = visible) { mAppVisible = visible; scheduleTraversals();if(! mAppVisible) { WindowManagerGlobal.trimForeground(); }}}void scheduleTraversals(a) {
        if(! mTraversalScheduled) { mTraversalScheduled =true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // To start the next refresh, traverse the View tree
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
     }
}
Copy the code

Look at the mTraversalRunnable

 final class TraversalRunnable implements Runnable {
        @Override
        public void run(a) { doTraversal(); }}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
 void doTraversal(a) {
        if (mTraversalScheduled) {
            mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); performTraversals(); }}Copy the code

In TraversalRunnable, doTraversal is executed. And perform performTraversals() in doTraversal. See the familiar performTraversals()? Yeah, I just started drawing the View here.

In view otimPL performTraversals(), this method is very long (about 800 lines of code) and the general flow is as follows

  1. Determine if the view size needs to be recalculated and execute performMeasure() if so
  2. Whether to relocate the location,performLayout()
  3. Do I need to redraw performDraw()?

So what causes the View to be redrawn? There are three main reasons

  • Changes in the internal state of the view itself (enable, pressed, etc.) may cause a redraw
  • View added or removed from View
  • The size and visibility of the View itself has changed

View drawing process

In the previous section, performTraversals() are performed by WMS IPC calls. The general drawing process for a View is

Traversals from performMeasure() -> performLayout() -> performDraw().

So let’s look at performMeasure()

//ViewRootImpl
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final booleanneedsLayout = specChanged && (sAlwaysRemeasureExactly || ! isSpecExactly || ! matchesSpecSize);if (forceLayout || needsLayout) {
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // Call the onMeasure method hereonMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; }}}Copy the code

Finally, the measure method of the View is called, and the measure() method of the View is defined as final to ensure that the whole process is executed. PerformLayout () and performDraw() are similar processes.

The custom View for the programmer, only need to pay attention to he offered some corresponding method, onMeasure/onLayout/ontouch. About this knowledge of the online introduction of a lot of information, it is also easy to see the View and ViewGroup inside the code, LinerLayout is recommended to see the source code to understand this part of the knowledge, here does not expand in detail.

Android drawing principle analysis

Android Screen Drawing

For drawing, let’s start with performDraw() and see how the process actually draws.

//ViewRootImpl
/ / 1
 private void performDraw(a) {
    try {
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}/ / 2
  private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;
    if(! surface.isValid()) {return;
        }
        
     if(! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {return; }}/ / 3
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {

     Canvas canvas = mSurface.lockCanvas(dirty);
 }        
 
Copy the code

Look at the code execution flow, 1 -> 2->3, finally get the Java layer canvas, and then carry out a series of drawing operations. Canvas is obtained by suface.lockCanvas ().

So what is Surface? Here, Surface is just an abstraction. When the APP creates a window, WindowManager will be called to initiate a request to WMS service, carrying the Surface object. Only when it has been allocated a screen buffer can it really correspond to a window on the screen.

Take a look at the drawing architecture in the Framework. Better understand the Surface

Surface essentially just represents a plane. Drawing different patterns is obviously an operation rather than a piece of data. Android uses the Skia drawing driver library to draw on the plane, and uses Canvas to represent this function in the program.

Introduction of double buffering technology

In ViewRootImpl, we see after receiving the drawing news, instead of immediately draw calls scheduleTraversals, in scheduleTraversals call Choreographer. PostCallback (), this is because what again? This comes down to the principle of screen drawing (and is similar on all platforms except Android).

We all know that monitors refresh at a fixed rate, like 60Hz on the iPhone or 120Hz on the iPad Pro. When one frame of an image is drawn and ready to draw the next, the display emits a vertical sync signal, or VSync, so a 60Hz screen emits 60 such signals a second.

Generally speaking, in a computer system, CPU, GPU and display cooperate in a specific way: CPU submits the calculated display content to GPU, and GPU puts it into the frame buffer after rendering, and then the video controller takes the frame data from the frame buffer according to VSync signal and transmits it to the display.

But if the screen has only one buffer, then the VSync sync signal starts to refresh the screen, and you see a strip of data changing on the screen. To make the screen look like frames of data, there are usually two buffers (also known as double buffers). When the data is to be refreshed, it directly replaces the data in another buffer.

Double buffering technology, if you can not refresh the specific time (if 60HZ, is within 16ms) to complete the buffer data refresh, the screen sends VSync synchronization signal, can not complete the two buffer switch, then it will cause the phenomenon of lag.

Going back to scheduleTraversals(), where double buffering (or triple buffering) technology is used,Choreographer receives synchronization signals from VSync to start the screen refresh operation when the screen is refreshed.

At the end of the article

Android drawing principle analysis, the introduction of the end, the above content may have the wrong place, I hope the various gods to advise.

(If you are watching Flutter) Recommended reading

Principle of Flutter performance

Other related articles

  • Implementation of the Flutter PIP effect
  • Implementation of the Flutter sidebar and city selection UI
  • How to gracefully handle Android OnActivityResult, RequestPermissions

The resources

Android Kernel Anatomy