preface

Review, review, review

Learn View event distribution, like out-of-towners on the black car!

Major problems solved

View event distribution

Sliding conflict

Multiple measurement

Drawing process

Environment: API 29

directory

1. Window event transfer

Where do events come from? How is it delivered?

First, when you touch the screen, the top layer PhoneWindow catches the event (the event is passed from the driver to IMS, IMS communicates with WMS, and WMS is passed to the target window via ViewRootImpl) and calls the Activity’s dispatchTouchEvent, It then calls the DecorView’s dispatchTouchEvent (DecorView inherits FrameLayout, FrameLayout inherits ViewGroup) and finally the event goes to the ViewGroup’s dispatchTouchEvent method. If false is returned and neither event is consumed, the Activity’s onTouchEvent method is called.

Events to the ViewGroup actually follow a bottom-up process as well. One layer passes to the View, then processes the event back to the Activity.

2. View event passing

View event transfer, mainly see the ViewGroup dispatchTouchEvent method, this method abstracted pseudocode as follows

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}
Copy the code

If the child View is a ViewGroup, then it keeps calling this method to distribute, and then it goes back up.

This is a bottom-up process that uses a recursive, chain of responsibility model.

Why is it so designed?

  • The layout rules of a View, the deeper the nesting, the higher the display level, the higher the level is easier for multiple users to see

  • What you see is what you get. What do you see and what do you touch

3. View event distribution

Let’s start with an overview of the event distribution (lite version) process

From the previous View event passing, we can see the core point inside the distribution:

  • Whether the current View needs to block touch events

  • Whether you need to continue distributing touch events to child views

  • How do I distribute touch events to child Views

Our code is in a dispatchTouchEvent according to the core point analysis

1. Intercept events

final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! = null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0; if (! disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { intercepted = true; }Copy the code

When the event is ACTION_DOWN, MFirstTouchTarget = null (not null when a child View catches the event), and mGroupFlags = FLAG_DISALLOW_INTERCEPT, It then calls its own onInterceptTouchEvent method to determine whether an interception is needed.

2. Distribution events

If the event is not intercepted, intercepted = false

if (! canceled && ! intercepted) { .... / / comment 1 if (actionMasked = = MotionEvent. ACTION_DOWN | | (split && actionMasked = = MotionEvent. ACTION_POINTER_DOWN) | | actionMasked == MotionEvent.ACTION_HOVER_MOVE) { if (newTouchTarget == null && childrenCount ! = 0) {... Final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); . // comment 3 if (! child.canReceivePointerEvents() || ! isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; }... / / comment if 4 (dispatchTransformedTouchEvent (ev, false, child, idBitsToAssign)) {newTouchTarget = addTouchTarget (child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; }... / / comment 5 if (mFirstTouchTarget = = null) {/ / comment 5.1 handled = dispatchTransformedTouchEvent (ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target ! = null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else {/ / comment 5.2 final Boolean cancelChild = resetCancelNextUpFlag (child) target. | | intercepted. if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; }... }}Copy the code

The first time you touch the screen, the ACTION_DOWN event is triggered, so comment 1 is true.

Comment 2 iterates through all of the child Views

Comment 3 is to check whether the event is in the child View coordinate range and the child View is not animated

Note 4 is to distribute the event to the sub-view. If the sub-view captures the event successfully, it will use mFirstTouchTarget to store all sub-views that need to be distributed. The structure is a linked list structure.

Note 5: If mFirstTouchTarget is null, the child View was intercepted during ACTION_DOWN processing, or the touch did not touch any child views, and then comment 5.1 is triggered. Comment 5.1 actually calls its own onTouchEvent to handle the event.

Note 5.2 will judge, if the intercept View distribution, can give a child View is transferred in a dispatchTransformedTouchEvent ACTION_CANCEL event

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part  is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; }... }Copy the code

So isn’t intercepted in comment 5.2 false? After all, the ACTION_DOWN event was not intercepted at the beginning. Is it possible that intercepted was changed to true for other events

if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! = null) {// comment 1 Final Boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT)! = 0; if (! disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { intercepted = true; }Copy the code

We can see that for the first time without intercept, will go to the above code comments 1, then just disallowIntercept to false, onInterceptToucheEvent is true, then intercepted will to true. When onInterceptTouchEvent returns false, The child View dispatchTouchEvent returns true, in which case the parent onInterceptTouchEvent returns true causing intercepted to reset to true, The child View will receive an ACTION_CANCEL touch event.

The process basically checks to see if the current ViewGroup needs to intercept the event, then distributes the event to the child View, then distributes the event to the child View according to the mFirstTouchTarget, and the distributed event will not be distributed again.

If mFirstTouchTarget is not null, that is, the child View captures the touch event, then the current and future events (ACTION_MOVE, ACTION_UP, etc.) are directly handed over to the View stored in mFirstTouchTarget for processing.

4. View sliding conflict

In simple terms, sliding conflict scenarios can be divided into three parts

  • ViewPager + Fragment the external and internal sliding directions are inconsistent

  • External sliding direction and internal sliding direction consistent ViewPager + transverse RecyclerView

  • Nested ViewPager + Fragment + RecyclerView

Solution Template

  • External interception

    Public Boolean onInterceptTouchEvent (MotionEvent event){Boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); Switch (event.getAction()) {case motionEvent.action_down :// False must be returned for ACTION_DOWN events, Once intercepted, subsequent events cannot be passed to the child View intercepted = false; break; ACTION_MOVE:// For ACTION_MOVE events, decide whether to intercept if (parent needs the current event) {intercepted = true; } else { intercepted = flase; } break; } case MotionEvent.ACTION_UP:// The onClick event is called in the UP event. If the child View's onClick event is intercepted, it will not trigger intercepted = false; break; default : break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted; }Copy the code
  • Internal interception method

    Public Boolean onInterceptTouchEvent (MotionEvent event) {int onInterceptTouchEvent (MotionEvent Event) {int onInterceptTouchEvent (MotionEvent Event) { action = event.getAction(); if(action == MotionEvent.ACTION_DOWN) { return false; } else { return true; }} Public Boolean dispatchTouchEvent (MotionEvent) {int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction) { case MotionEvent.ACTION_DOWN: parent.requestDisallowInterceptTouchEvent(true); // True prevents the parent from intercepting break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastX; int deltaY = y - mLastY; If (the parent container need such click events) {parent. RequestDisallowInterceptTouchEvent (false); } break; case MotionEvent.ACTION_UP: break; default : break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); }Copy the code

For internal neutron View intercept method called by the parent. RequestDisallowInterceptTouchEvent (true)

@Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0)) { // We're already in this state, assume our ancestors are too return; } if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } if (mParent ! = null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); }}Copy the code

We can see that if we pass true and mGroupFlags & FLAG_DISALLOW_INTERCEPT = 0, then the mGroupFlags value becomes FLAG_DISALLOW_INTERCEPT.

Let’s look at the block of code for event distribution interception in section 3

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0; if (! disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; }Copy the code

As you can see, disallowIntercept is set to true, and intercepted is set to false to disallow superview interception.

conclusion

Call child. DispatchTouchEvent recursively and then return,true means the event is consumed. The whole process is recursive and backtracking.

Each time you execute dispatchTouchEvent you can determine whether you need to intercept the current event or pass it on to the next level

If the listener is set to return true, the event will be consumed and the onTouchEvent will not be executed. Otherwise, if you do an onTouchEvent, you’re going to do either onClick or onLongClick internally based on the action

ACTION_MOVE and ACTION_UP will not be intercepted. Corresponding to onInterceptTouchEvent, ACTION_MOVE and ACTION_UP will only be called once. The mFirstTouchTarget flag is null, and subsequent intercepts are based on this flag. Any View that captures a touch goes to the mFirstTouchTarget.

In the parent view onInterceptTouchEvent returns false, If onInterceptToucheEvent returns true during a MOVE, resetTouchState is passed to the child control ACTION_CANCEL event

There are internal intercepts and external intercepts when dealing with sliding conflicts

The external intercepting method mainly overwrites the onInterceptTouche method of the parent View. It is important that ACTION_DOWN events are not shielded.

Internal intercept is rewriting the parent view onInterceptTouchEvent, then the child view requestDisallowInterceptTouchEvent method is used to change mGroupFlags value, If FLAG_DISALLOW_INTERCEPT is included, interception is not allowed and the parent view’s onInterceptTouchEvent method is called

5. Drawing process of View

The starting process of an Activity is described from the startActivity Context, which is ContextImpl’s startActivity implementation, and then the internal Instrumentation will try to start the Activity. This is a cross-process process. It calls AMS’s startActivity method, and when AMS validates the Activity, it calls back to our process via ApplicationThread, which is a Binder. The callback logic is done in the Binder thread pool, so it needs to be cut back to the UI thread via Handler H. The first message is LAUNCH_ACTIVITY, which corresponds to the handleLaunchActivity. This method creates and starts the Activity. Then, in the Activity’s onResume, the content of the Activity will start rendering onto the Window and start drawing until we can see it. (Four components can start the source code process search, the author wrote before typesetting is not good, has been deleted, no longer subsequent analysis)

First we need to understand that Window is a PhoneWindow created in the handleLaunchActivity method when the Attach method is called after the Activity is created.

The onCreate method in the activity calls setContentView to associate PhoneWindwo with a DecorView, and then the handleResumeActivity stage makes a remote call to add the View to the Window, This phase also creates the ViewRootImpl and calls the requestLayout method.

For details about View refresh mechanisms and Surface refresh mechanisms, see The Android Screen refresh mechanisms VSync and Choreographer

RequestLayout is a function that calls performTraversals within the requestLayout. The internal traversals are called as follows. For performTraversals, see the link.

PerformMeasure Measures the onMeasure function

PerformLayout Performs onLayout operations

PerformDraw performs the onDraw operation

6. Measurement process of View

MeasureSpec 2 bits higher indicates SpecMode and 30 bits lower indicates SpecSize. SpecMode has three types as follows

  • The Parent of The UNSPECIFIED container has no restrictions on the View as to how large it can be

  • EXACTLY the exact size, the value of SpecSize, generally corresponds to both match_parent and concrete data modes

  • The AT_MOST parent container specifies an available size, SpecSize, that the View cannot be larger than

Key code

childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
Copy the code

We determine the DecorView’s MeasureSpec with the Window size and getRootMeasureSpec

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        case ViewGroup.LayoutParams.MATCH_PARENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
Copy the code

Then call the onMeasure method of the DecorView via the performMeasure method (DecorView descends from FrameLayout, and onMeasure is also in FrameLayout)

for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() ! = GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) {  mMatchParentChildren.add(child); }}}}Copy the code

Let’s analyze the measureChildWithMargins method

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
​
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
​
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
Copy the code

The root determines child elements’ MeasureSpec based on the parent View’s MeasureSpec and the View’s own LayoutParams. The key method is getChildMeasureSpec

Public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }Copy the code

The main function is to determine child elements’ MeasureSpec from the parent View’s MeasureSpec and the View’s Own LayoutParams.

The general rules are as follows

When a View has a fixed width and height, whatever parent’s MeasureSpec is, the View’s MeasureSpec is the exact size of LayoutParams. When the width and height of the View is match_parent, if the parent is in exact mode, then the View is also in exact mode and its size is the remaining space of the parent. If the parent is in maximum mode, then the View is also in maximum mode and its size does not exceed the remaining space of the parent. When a View’s width and height is wrAP_content, the View is always maximized and does not exceed the amount of space left in the parent container, regardless of whether the parent container is in precise or maximized mode.

When you customize a View, do you need to override the onMeasure method. If you don’t override the onMeasure method, do you set wrap_content or match_parent to parentSize and fill the screen? From the above introduction, you should know the bottom line.

Here’s another common question: Why is onMeasure called twice?

This may vary with different controls and apis. For example, API 16-19 May execute onMeasure twice, onLayout twice, and onDraw once. While API 21-23 performs 3 onMeasure, 2 onLayout and 1 onDraw operations, API 24-25 performs 2 onMeasure, 2 onLayout and 1 onDraw operations.

Some netizens say that performTravels was called twice, in fact, I think it is true. At the end of the performTravesls code

boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || ! isViewVisible; if (! cancelDraw) { if (mPendingTransitions ! = null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); } else { if (isViewVisible) { // Try again scheduleTraversals(); } else if (mPendingTransitions ! = null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); }}Copy the code

The first time I go here, cancelDraw is false because isViewVisible is true. This is because the requestLayout method (in this case, by sending an asynchronous barrier message) is used to start the Activity and the setVisibility method is immediately called in the DecorView. So in Android 10, there’s actually one onMeasure, not multiple onMeasures.

Let’s take a look at the android 9 API

boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || ! isViewVisible; if (! cancelDraw && ! newSurface) { if (mPendingTransitions ! = null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); } else { if (isViewVisible) { // Try again scheduleTraversals(); } else if (mPendingTransitions ! = null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); }}Copy the code

In this case, the newSurface is false. OnDraw can take the Canvas and write data to the Surface buffer only when the Surface is ready.

if (! hadSurface) { if (mSurface.isValid()) { // If we are creating a new surface, then we need to // completely redraw it. Also, when we get to the // point of drawing it we will hold off and schedule // a new traversal instead. This is so we can tell the // window manager about all of the windows being displayed // before actually drawing them, so it can display then // all at once. newSurface = true; . } public boolean isValid() { synchronized (mLock) { if (mNativeObject == 0) return false; return nativeIsValid(mNativeObject); }}Copy the code

** And you might ask? ** I’m not asking how many times the onMeasure is executed at startup, I’m asking how many times the method is executed like FrameLayout.

First of all, we need to understand the process, we are measured first, then the parent layout to put the child View in place, and then draw. That measurement process, is a process from the top down, the child View after measuring the results of the summary back to the parent layout, the parent layout then set their own width and height, that is, the first calculation of the child and then calculate their own. It is the first time to measure the View Tree with the preset width and height. If you are not satisfied with the result, you will measure the View Tree again until you are satisfied.

7. View layout process

PerformLayout actually calls FrameLayout’s layoutChildren method for the layout, so there’s not much to analyze here.

The invalidate method must be executed on the main thread, and the postInvalidate method can be executed on the child thread. This method does not necessarily trigger the View measure or layout.

In a View measure, if the onMeasure method is triggered, the View’s PFLAG_FORCE_LAYOUT flag bit needs to be set

public final void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; . if (forceLayout || needsLayout) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; }}Copy the code

From the above code, you can see that if forceLayout is true, the onMeasure method is executed, Performed in requestLayout mPrivateFlags | = PFLAG_FORCE_LAYOUT and invalidate did not set this flag.

The same goes for onLayout

public void onLayout(int l, int t, int r, int b) if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); . }Copy the code

As you can see, onLayout is executed when the View position is changed or the PFLAG_LAYOUT_REQUIRED flag bit is added. When invalidate is called, onLayout will not be called if the View has not changed or if the flag bit is not set.

You might ask how invalidate updates the view, but scheduleTraversals -> performTravels.

If postInvalidate is executed on the main thread, it is generally done by internally switching to the main thread through a Handler and then calling invalidate. Many open source libraries do this.

8. View drawing process

What does the drawing process mainly do?

draw: canvas = mSuface.lockCanvas(); drawBackground(canvas); . if (! verticalEdges && ! horizontalEdges) { // Step 3, draw the content onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); surface.unlockCanvasAndPost(canvas);Copy the code

It is mainly to draw the background of the View, draw the content of the View itself, distribution of DRAW events, recursive call sub-view draw events.

A buffer is obtained via lockCanvas, which is then drawn by the Java layer through the View, and finally submitted via unlockCanvasAndPost.

Here the lockCanvas gets the backCanvas, and the view refresh uses double buffering.

Each time the data submitted by CPU is cached in the Back Buffer, and then the GPU rasterizes the data in the Back Buffer and swaps it into the Frame Buffer. Finally, the screen retrieves the data from the Frame Buffer for display. The GPU is responsible for periodically exchanging data in the Back Buffer and Frame Buffer to ensure that the latest content is displayed on the screen. When the CPU is writing data to the Back Buffer, the GPU will lock the Back Buffer. If it is the time to swap two buffers, this swap will be ignored, which will directly result in the Frame Buffer still saving the data of the previous Frame, that is, the previous content. Frame loss for Android.

Refresh data:

The application first applies for a buffer from the system service, the system returns the buffer, draws the buffer, and submits the buffer to the system service. The system service writes the buffer to the screen buffer area, and the screen refreshes at a certain frame rate. Every time the buffer area is refreshed, the image is scanned and read out. If there is no new data in the buffer area, the screen keeps using the old data and looks the same. (There is not only one buffer, one buffer to write and one buffer to read).