The working principle of

In Android, Window is used as the screen abstraction, and the concrete implementation class of Window is PhoneWindow. WindowManager and WindowManagerService work together to manage what is displayed on the screen.

Inside WindowManager, the WindowManagerGobal method is really called, and the addView method is used to add views. In Windows Manager Global, the View file is the final way to add View and layout parameters to the screen. In fact, the View tree is really managed by the View tree.

The ViewRootImpl invokes the addToDisplay method of the Session in the WMS by calling the methods defined by the IWindowSession interface through the Binder communication mechanism.

In WMS, a Surface is assigned to Windows and the Window display order is determined. Surface is responsible for displaying the interface, Window itself does not have the ability to draw; WMS hands the surfaces over to SurfaceFlinger, which mixes them up and draws them onto the screen.

WMS executes the request to add a Window in the View wrootimPL and does four main things:

  • The Window to be added is checked and the following action is not performed if the conditions are not met
  • WindowToken related processing, for example, if some window types need to provide WindowToken, the adding logic will be terminated if it is not provided; Some Windows require Windows Tokens to be created implicitly by WMS.
  • Creation and related processing of WindowState, associating WindowToken with WindowState.
  • Create and configure DisplayContent in preparation for adding Windows to the system.

View builds the View tree

In the Activity setContentView method, the PhoneWindow setContentView method is called, and a series of calls are made to parse the layout file into a View, And stuff it into the mContentView of a DecorView with ID R.D.C. Tent.

The DecorView itself is a FrameLayout that also hosts the StatusBar and NavigationBar.

HandleResumeActivity is then called to wake up the Activity. In this method, you add a DecorView via The WindowManager addView method, which uses ViewRootImpl to build the view tree, You are also responsible for the measurement, layout, drawing of View trees, as well as controlling the refresh of views through Choreographer.

Finally, the setView method of ViewRoot is called to assign the DecorView to the ViewRoot and the updateViewLayout method is called.

updateViewLayout

Within this method, a scheduleTraversals method is called, which internally executes a Runnable through the postCallback of a Choreographer object, In this Runnable, the doTraversal method is called.

PerformTraversals is called in doTraversal, and performTraversals causes the ViewTree to start the View:

  1. RelayoutWindow, which internally calls the Relayout method of IWindowSession to update Windows, associating the Surface of the Java layer with the Surface of the Native layer;
  2. PerformMeasure, internal call to View measure;
  3. PerformLayout, internal call View layout;
  4. PerformDraw, internal call to View’s Draw and that completes the Window update.

The ViewRootImpl interacts with the WMS

From the above flow, the ViewRootImpl setView method actually calls the WMS Session addToDisplay method to notify the WMS to update the View, WMS associates each Window with a WindowState. In addition to this, one important thing that The setView of View RotimPL does is register an InputEventReceiver, which is related to View event distribution.

In WindowState, the SurfaceSession is created, which constructs a SurfaceComposerClient object in the Native layer that acts as a bridge between the application and the SurfaceFlinger.

conclusion

At the bottom of the View is a Window container, which holds the View.

During the Window addition process, WMS is notified to add the Window through the setView of ViewRootImpl in Windows Manager.

The Viewrotimpl invokes the WMS Session addToDisplay method with the Binder mechanism to send the request.

When WMS receives a request to add a Window, it assigns a Surface to the Window and processes it, associating it with a WindowState.

WMS hands the Surface over to SurfaceFlinger and renders the view. WindowState also creates SurfaceSession, which communicates with SurfaceFlinger.

View interaction, which is registered with the setView of viewrotimPL, registers an InputEventReceiver to receive events.

Drawing process

The ViewRootImpl final performTraversals method calls performMeasure, performLayout, and performDraw in turn.

Measure

The first step is to get the MeasureSpec of the DecorView’s width and height and then execute the performMeasure process.

In performMeasure, because of the View tree structure, it calls the measure methods of sub-views in a recursive way.

Fun Measure(args) {onMeasure(args)} fun onMeasure(args) {val size = getDefaultSize( setMeasureDimension(width, If (this is ViewGroup) {// If (this is ViewGroup) {// If (this is ViewGroup) {// If (this is ViewGroup) {// If (chidren.foreach {View -> view.measure() // call the child View Measure}}}Copy the code

Layout

The performLayout method calls the DecorView’s Layout method first and then recursively calls the child View, determining the coordinates of the View’s four vertices and the actual View’s width/height.

If the View size changes, the onSizeChanged method is called back.

If the View changes, the onLayout method is called back.

  • The onLayout method is an empty implementation in the View.
  • In a ViewGroup, the layout method of the child View is iterated recursively.

Draw

Finally, there is the performDraw method, which calls the drawSoftware method, which first gets a Canvas object from the mSurface#lockCanvas property and passes it as an argument to the DecorView’s Draw method.

This method calls the View’s Draw method, drawing the View’s background and then the View’s contents.

If there is a child View will call the draw method of the child View, layer upon layer recursive call, the final completion of the drawing.

After these three steps are complete, the Activity’s makeVisible method is called at the end of the ActivityThread’s handleResumeActivity, which sets the DecorView to visible.

Screen refresh mechanism

In a typical Display system, there are generally three parts: CPU, GPU and Display. CPU is responsible for calculating frame data and handing the calculated data to GPU, which will render the graphics data and store it in buffer(image buffer) after rendering. Then the Display (screen or Display) is responsible for rendering the data in the buffer onto the screen. The diagram below:

Basic concept

Screen refresh rate

The number of screen refreshes in a second (how many frames of image are displayed in a second), in Hz (Hertz), such as the common 60 Hz. The refresh rate depends on the hardware’s fixed parameters (which do not change).

Progressive scan

Instead of putting an image on the screen all at once, the display scans from left to right, top to bottom, showing the entire screen in sequence, pixel by pixel, though the process is too fast for the human eye to notice the change. Taking a screen with a refresh rate of 60 Hz as an example, this process is 1000/60 ≈ 16ms.

Frame rate

The number of frames drawn by the GPU in one second (unit: FPS). In the movie industry, for example, 24 frames is enough to make the picture run very smoothly. Android, on the other hand, has a more fluid 60 FPS, meaning the GPU can draw up to 60 frames per second. The frame rate changes dynamically. For example, when the picture is still, there is no drawing operation on GPU, and the screen refreshes the data in buffer, that is, the last frame data operated by GPU.

Torn picture

The data in one screen comes from two different frames, and the picture will show a sense of tearing, as shown below:

Double cache

The reason for the tear

The screen refresh frequency is fixed, for example, every 16.6ms to display a frame from the buffer. Ideally, the frame rate and refresh frequency are consistent, that is, every frame drawn, the monitor displays a frame. However, CPU/GPU write data is uncontrollable, so some data in the buffer will be rewritten before it is displayed at all, that is, the data in the buffer may come from different frames, and when the screen is refreshed, it does not know the state of the buffer. Therefore, the frame captured from buffer is not a complete frame, that is, the frame is torn.

To put it simply, during the Display process, the data in the buffer is modified by CPU/GPU, causing the picture to tear.

Double cache

Since the same buffer is used to draw the image and read the screen, it is possible to read an incomplete frame when the screen is refreshed.

Dual cache, so that draw and display have their own buffer: The GPU always writes a completed Frame of image data to the Back Buffer, whereas the display uses the Frame Buffer. The Frame Buffer does not change when the screen refreshes, and they swap when the Back Buffer is ready. The diagram below:

VSync

So when do I swap the two buffers?

If the Back buffer is intended to run after a frame has been completed, it will cause problems if the screen has not fully displayed the previous frame. It seems that you can only do this after the screen has processed a frame of data.

When a screen is scanned, the device needs to go back to the first row to enter the next cycle, with a time gap called the VerticalBlanking Interval(VBI). This point in time is the best time for us to swap buffers. Because the screen is not refreshing at this point, it avoids screen tearing during the swap.

**VSync ** is short for vertical synchronization, which uses the VerticalSync Pulse that emerged during the VBI era to ensure that dual buffers are exchanged at the optimal point in time. In addition, the swap refers to the respective memory address, which can be considered instantaneous.

Before Android4.1, the screen refresh also follows the dual-cache +VSync mechanism described above.

Take a look at what will happen in chronological order:

  1. Display Display frame 0 data, at this time CPU/GPU rendering frame 1, and need to be completed before Display next frame.
  2. Because rendering is timely, after Display is finished on frame 0, after VSync 1, the cache is swapped and then frame 1 is displayed normally.
  3. Frame 2 is then processed, not until the second VSync is about to arrive.
  4. When the second VSync comes, frame 1 is still displayed because frame 2 is not ready and the cache is not swapped. This situation, named “Jank” by the Android development team, occurs when frames are lost.
  5. When frame 2 data is ready, it will not be displayed immediately, but will wait for the next VSync to cache swap.

Because the rendering of the second frame was not timely, the first frame showed an extra VSync cycle and the second frame was lost.

Dual buffer swapping occurs when the Vsync signal arrives. After the exchange, the screen will take the new data in the Frame buffer, and the actual Back buffer at this time can be used by GPU to prepare the next Frame data. If the CPU/GPU starts operating when Vsync comes, there is a full 16.6ms, which should basically avoid Jank (unless CPU/GPU calculations go beyond 16.6ms). So how do you make CPU/GPU computing happen when Vsync comes along?

In order to optimize Display performance, the Android Display system has been reconstructed in Android 4.1 to implement Project Butter: the system will immediately start rendering the next frame after receiving the VSync pulse. As soon as the VSync notification is received (triggered every 16ms), the CPU/GPU immediately computes and writes data to buffer. The diagram below:

When the CPU/GPU synchronizes data processing according to the arrival of VSYNC signal, the CPU/GPU can have a complete 16ms time to process data, reducing jank.

What if the interface is complex and the CPU/GPU processing takes longer than 16.6ms? The diagram below:

  1. When the first VSync signal arrived, the GPU was still processing FRAME B, and the cache failed to be swapped, resulting in the repeated display of frame A.
  2. After B is finished, it has to wait for the next time due to the lack of VSync Pulse signal. So in the process, a lot of time is wasted.
  3. When the next VSync occurs, the CPU/GPU immediately performs the operation (frame A), and the cache is swapped, and the corresponding display corresponds to B. It seems normal at this point. Just because of the execution time is still more than 16 ms, lead to the next should perform the buffer exchange was delayed, so repeated again, then there are more and more “Jank”.

Why can’t the CPU handle the drawing work in the second 16ms?

The reason is that there are only two buffers. The Back buffer is being used by THE GPU to process the data of Frame B, and the content of the Frame buffer is used for Display Display. In this way, both buffers are occupied, and the CPU cannot prepare the data of the next Frame. Then, if another buffer is provided, the CPU, GPU, and display device can all use their own buffer without affecting each other.

3 cache Mechanism

Three-buffer is to add a Graphic Buffer on the basis of double Buffer mechanism, so as to maximize the use of idle time, the disadvantage of using more than one Graphic Buffer occupied memory.

  1. The first Jank was unavoidable. However, in the second 16ms period, CPU/GPU used the third Buffer to complete the calculation of C frame. Although A frame would still be displayed once more, the subsequent display would be relatively smooth, effectively avoiding the further aggravation of Jank.
  2. Note that in paragraph 3, frame A is computed, but not displayed until VSync 4 arrives, or if it is double-buffered, it will be displayed when VSync 3 arrives.

Triple buffering makes good use of the time spent waiting for VSync, reducing jank but introducing latency.

Choreographer

Before ViewRootImpl calls the updateViewLayout method to actually perform the drawing process, a Choreographer object is mentioned. Choreographer is the class used for rendering.

In the principle of screen refresh, we know that when a VSync signal arrives, the CPU/GPU immediately starts computing data and storing it into buffer, and then the actual implementation of this logic is Choreographer.

What it does is:

  • Start drawing only after receiving VSync signal to ensure complete 16.6ms of drawing and avoid randomness of drawing.
  • Coordinate timing of animation, input, and drawing.
  • The application layer will not use Choreographer directly, but rather more advanced apis such as those related to animation and View drawingValueAnimator.start(),View.invalidate() And so on.
  • Choreographer is commonly used by the industry to monitor application frame rates.

In Android source, Choreographer starts with the scheduleTraversals method of ViewRootImpl. When we use valueanimator.start (), view.invalidate (), we also end up with the scheduleTraversals method of the ViewRootImpl. All UI changes go to the scheduleTraversals() method of ViewRootImpl.

    void scheduleTraversals(a) {
        if(! mTraversalScheduled) {// Ensure that multiple changes at the same time are refreshed only once
            mTraversalScheduled = true;
            // Add a message barrier (asynchronous messages jump queue and execute first) to block synchronous messages and ensure that VSync is drawn as soon as it arrives
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code

. In this method, the final call mChoreographer postCallback () method, performs a Runnable, the Runnable will be the next VSync arrives, perform doTraversal ().

The performTraversals method is called inside doTraversal() for the rendering process.

The mChoreographer object, however, is initialized within the ViewRootImpl object constructor:

public ViewRootImpl(Context context, Display display) {
	// ...
	mChoreographer = Choreographer.getInstance();
	// ...
}
Copy the code

Take a look at the Choreographer class:

public final class Choreographer {
		private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue(a) {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            returnchoreographer; }};public static Choreographer getInstance(a) {
        returnsThreadInstance.get(); }}Copy the code

Choreographer, like Looper, is thread singleton.

Its postCallaback method, whose first argument is an int enumeration, includes:

	  // Input events, executed first
    public static final int CALLBACK_INPUT = 0;
    // animation, second execution
    public static final int CALLBACK_ANIMATION = 1;
    // Insert updated animation, third execution
    public static final int CALLBACK_INSETS_ANIMATION = 2;
    // Draw, execute fourth
    public static final int CALLBACK_TRAVERSAL = 3;
    // Submit, execute,
    public static final int CALLBACK_COMMIT = 4;
Copy the code

Internal call postCallbackDelayedInternal:


    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
				// ...
        synchronized (mLock) {
        	// The current time
            final long now = SystemClock.uptimeMillis();
            // Add delay time
            final long dueTime = now + delayMillis;
            // Take the corresponding type of CallbackQueue to add a task
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            if (dueTime <= now) {
            	// Execute immediately
                scheduleFrameLocked(now);
            } else {
            	ScheduleFrameLocked ()
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); }}}Copy the code

The last internal execution is the scheduleFrameLocked method.

As you can guess from the postCallback method name and Looper structure, the Handler mechanism is used to handle VSync.

Choreographer has its own internal message queues, mCallbackQueues, with its own handlers.

    private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                	// Perform doFrame, the drawing process
                    doFrame(System.nanoTime(), 0);
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                	// Request a VSYNC signal, such as when you need to draw a task
                    doScheduleVsync();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                	// Tasks that need to be delayed will eventually execute the above two events
                    doScheduleCallback(msg.arg1);
                    break; }}}Copy the code

ScheduleFrameLocked is also executed internally by the doScheduleCallback method.

In scheduleFrameLocked, the logic is something like:

  • If VSYNC is not enabled, the MSG_DO_FRAME message is sent directly to the FrameHandler. The doFrame method is executed directly.
  • Android 4.1 system default after open VSYNC and methods in the construction of the Choreographer will create a FrameDisplayEventReceiver scheduleVsyncLocked method will apply VSYNC signal through it.
  • Call isRunningOnLooperThreadLocked method, its internal according to which determine whether the original thread, or sending a message to the FrameHandler. ScheduleVsyncLocked is eventually called to request VSYNC signals.

FrameHandler is used to send asynchronous messages that are processed first by queue jumpers. Delay messages are sent to tasks with delays, those not in the original thread are sent to the original thread, and those not enabled with VSYNC are directly drawn using the doFrame method.

Moving on, how do scheduleVsyncLocked request VSYNC signals?

    private void scheduleVsyncLocked(a) {
        mDisplayEventReceiver.scheduleVsync();
    }
Copy the code
    public DisplayEventReceiver(Looper looper, int vsyncSource) {
        if (looper == null) {
            throw new IllegalArgumentException("looper must not be null");
        }
        mMessageQueue = looper.getQueue();
        // Register the VSYNC listener
        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, vsyncSource);
        mCloseGuard.open("dispose");
    }
Copy the code

The constructor in DisplayEventReceiver creates a VSYNC listener for IDisplayEventConnection through JNI.

FrameDisplayEventReceiver scheduleVsync () is in DisplayEventReceiver:

    public void scheduleVsync(a) {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
        	// The onVsync method will be called back if the VSYNC interrupt signal is requestednativeScheduleVsync(mReceiverPtr); }}Copy the code

ScheduleVsync calls the native method nativeScheduleVsync to apply for the VSYNC signal. The receiving callback for the VSYNC signal is onVsync(). In FrameDisplayEventReceiver concrete implementation:

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;
      	// ...

        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            long now = System.nanoTime();
            if (timestampNanos > now) {
                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001 f)
                        + " ms in the future! Check that graphics HAL is generating vsync "
                        + "timestamps using the correct timebase.");
                timestampNanos = now;
            }

            if (mHavePendingVsync) {
                Log.w(TAG, "Already have a pending vsync event. There should only be "
                        + "one at a time.");
            } else {
                mHavePendingVsync = true;
            }

            mTimestampNanos = timestampNanos;
            mFrame = frame;
            // Run (doFrame()); // Run (doFrame())
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run(a) {
            mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); }}Copy the code

In onVsync(), the receiver itself is passed in as a Runnable asynchronous message MSG, and mHandler is used to send the MSG, finally executing the doFrame method.

Finally, the doFrame method:

    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            // ...
						// Expected execution time
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            // Whether the timeout period exceeds the time of one frame
            final long jitterNanos = startNanos - frameTimeNanos;
            if (jitterNanos >= mFrameIntervalNanos) {
            		// Count the number of frames lost
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                		// Drop more than 30 frames and print Log prompt
                  	Log.i(TAG, "Skipped " + skippedFrames + " frames! ");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                // ...
                frameTimeNanos = startNanos - lastFrameOffset;
            }
            // ...
            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            // Frame flag bit is restored
            mFrameScheduled = false;
            // Record the last frame time
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
						// Execute tasks in type order
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
            / / input first
          	mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
          	// animation
            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
						// CALLBACK_TRAVERSAL
            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
						/ / commit finally
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally{ AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

Redraw related methods

  • invalidate

    After calling the invalidate () method will call up step by step father View relevant methods, finally with the control of the Choreographer ViewRootImpl. PerformTraversals () method. Measure and Layout processes are executed only if conditions are met, otherwise only DRAW processes are executed.

    The execution of the DRAW process depends on whether hardware acceleration is enabled:

    • Turn hardware acceleration off and all child views from DecorView down are redrawn.
    • With hardware acceleration enabled, only views that call the invalidate method will be redrawn.
  • requestLayout

    RequestLayout calls performMeasure, performLayout, and performDraw in sequence.

    The caller View and its parent View will redo the measure, Layout process from top to bottom, and will not normally execute the draw process.

    So you can use the Invalidate method when you only need to redraw, or you can use the requestLayout method if you need to remeasure and rearrange, which doesn’t necessarily redraw. So if you want to redraw, you can call the invalidate method manually.

Dispatching events

MotionEvent

Event distribution is the process of distributing a MotionEvent to a View. When a MotionEvent is generated, the system needs to pass the event to a specific View, which is the distribution process.

Click on the distribution method of events

There are three methods involved in event distribution:

  • dispatchTouchEvent

    This method must be called if the event can be passed to the current View, and the return result is affected by the current View’s onTouchEvent and the next-level View’s dispatchTouchEvent method, indicating whether the current event is consumed.

  • onInterceptTouchEvent

    Called within dispatchTouchEvent to determine whether or not to intercept an event. If the current View intercepted an event, this method is not called again within the same sequence of events and returns a result indicating whether or not to intercept the current event.

  • onTouchEvent

    Called in the dispatchTouchEvent method to handle the click event and returns a result indicating whether the current event is consumed. If not, the current View cannot receive the event again in the same sequence of events.

Event distribution is divided into two cases, View and ViewGroup:

View
public boolean dispatchTouchEvent(MotionEvent event) { // ... if (onFilterTouchEventForSecurity(event)) { // ... ListenerInfo li = mListenerInfo; if (li ! = null && li.mOnTouchListener ! = null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } // Execute your own onTouchEvent if (! result && onTouchEvent(event)) { result = true; }} / /... return result; }Copy the code

The event distribution of a View is performed first by onTouchListener and then by onTouchEvent to return whether the event is consumed.

ViewGroup
/ / pseudo code
public boolean dispatchTouchEvent(MotionEvent event) {
		// ...
		// 1. Prioritize interception
		boolean intercepted = onInterceptTouchEvent(ev);
		if(! canceled && ! intercepted) {// Find the child View that consumes the event
        final float x = isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
				final float y = isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
				final ArrayList<View> preorderedList = buildTouchDispatchChildList(); // List of subviews
				for (int i = childrenCount - 1; i >= 0; i--) {
						if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
								break; }}}if (mFirstTouchTarget == null) {
				handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
    }
    // ...
}
Copy the code

In the ViewGroup, onInterceptTouchEvent is executed first to determine whether it needs to be intercepted. If not, onInterceptTouchEvent will continue to pass through the sub-views

DispatchTransformedTouchEvent method, continue to distribute events:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
		if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);	// [2] dispatchTouchEvent itself
        } else {
            handled = child.dispatchTouchEvent(event);	/ / 【 1 】
        }
        event.setAction(oldAction);
        return handled;
    }
  	if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) { 
                handled = super.dispatchTouchEvent(event); // [2] dispatchTouchEvent itself
            } else {	
              	// Event response with offset
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                handled = child.dispatchTouchEvent(event); / / 【 1 】

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }
  	// Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix()); 
        }
        handled = child.dispatchTouchEvent(transformedEvent); / / 【 1 】
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}
Copy the code

DispatchTransformedTouchEvent according to incoming child View to continue to call dispatchTouchEvent, distributed downward.

When super.DispatchTouchEvent (event) is called, the dispatchTouchEvent method of ViewGroup’s parent View class is called. So it checks the onTouchListener, and then it executes the onTouchEvent.

Real distribution process

A ViewGroup contains a View:

  1. The ViewGroup first calls dispatchTouchEvent for event distribution

  2. The ViewGroup calls onInterceptTouchEvent to determine whether to intercept

    • Return true, intercepts the event for processing, the event will not continue to be distributed to the child View, calling its own onTouchEvent.

    • Return false, and go to Step 3.

  3. Call the dispatchTouchEvent of the child View

  4. The child View calls onTouchEvent to determine whether the event is consumed

    • Return true, the event will not continue to be distributed to the child View, calling its own onTouchEvent.
    • Return false, do not respond to the event.

Caton analysis

Caton’s common cause

  1. Frequent requestLayout

    Frequent invocation of rquestLayout() will result in frequent computations within a frame, making the Traversal process longer, so frequent requestLayout should be avoided. For example, in a list, add an animation to an item.

  2. The UI thread is blocked

    Classic don’t do time-consuming operations on the UI site.