Those who don’t understand the Android View event dispatch process have seen it~

Ever since Joe launched the iPhone, touch-based operations have become the standard input method for smart devices in the 21st century. For Android, which is also a smart operating system, it is no exception. Events, especially touch events, are very important for mobile application development, and they must be mastered by developers. Here we will discuss the Event system in Android View, focusing on the event dispatch process.

Input Event Summary

Classification of events

For the Android system, user input events are divided into two categories, one is KeyEvent, which is an event generated by hardware, or more accurately, an event generated by a non-touch gesture, usually including hardware buttons such as volume keys, power keys, system Navigation (HOME, BACK, and MENU) and peripherals (such as external devices, keyboards, selfie sticks, etc.) system layers will also be uniformly mapped and converted into KeyEvents and sent to the current Window window (current process).

There is another category that specifically refers to the touch gesture events generated by uncontrolling the screen. It is MotionEvent. Why is it not called TouchEvent? Because the original Android version supports sliding balls. There is no such device now, but the name is just like that. handed down. This event is specially handled by the view system view tree, and this article will also focus on such events.

where does the event come from

To put it simply, events originate from hardware, such as screens or buttons. This is nonsense. Knowing this is meaningless. After the hardware generates an electronic signal, it will be transmitted to the kernel through the driver, and the kernel will report it to the input system and then to wms. (Windows Manager Server), will eventually come to Window here. For the application layer, it can be understood that the events all come from the Window object.

who receives the event first

For the GUI application layer, wms is the event source, so the ViewRootImpl object is the first to receive the event. ViewRootImpl does not directly dispatch the event to the view tree, but to the DecorView first. The host component has a dedicated Receive the callback of the event, and then the event reaches the current host component such as Activity or Dialog to see if it is willing to process it. If it does not process it, the event will be dispatched to the GUI view system, that is, the view tree. This time Instead of going through the ViewRootImpl object, the Window object directly calls the dispatchTouchEvent or dispatchKeyEvent of the root node.

   15:57:02.254   W/System.err: java.lang.Exception: Stack trace
   15:57:02.255   W/System.err:     at java.lang.Thread.dumpStack(Thread.java:1348)
   15:57:02.256   W/System.err:     at net.toughcoder.view.ViewWindowExampleActivity.dispatchKeyEvent(ViewWindowExampleActivity.java:107)
   15:57:02.256   W/System.err:     at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:342)
   15:57:02.256   W/System.err:     at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5053)
   15:57:02.257   W/System.err:     at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4921)
   15:57:02.257   W/System.err:     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
   15:57:02.258   W/System.err:     at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4495)
   15:57:02.258   W/System.err:     at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4461)
   15:57:02.259   W/System.err:     at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4601)
   15:57:02.259   W/System.err:     at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4469)
   15:57:02.259   W/System.err:     at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4658)
   15:57:02.260   W/System.err:     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
   15:57:02.260   W/System.err:     at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4495)
   15:57:02.260   W/System.err:     at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4461)
   15:57:02.261   W/System.err:     at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4469)
   15:57:02.261   W/System.err:     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
   15:57:02.261   W/System.err:     at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4495)
   15:57:02.262   W/System.err:     at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4461)
   15:57:02.262   W/System.err:     at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4634)
   15:57:02.263   W/System.err:     at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:4795)
   15:57:02.263   W/System.err:     at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2571)
   15:57:02.263   W/System.err:     at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:2081)
   15:57:02.264   W/System.err:     at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:2072)
   15:57:02.264   W/System.err:     at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:2548)
   15:57:02.265   W/System.err:     at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:141)
   15:57:02.265   W/System.err:     at android.os.MessageQueue.nativePollOnce(Native Method)
   15:57:02.265   W/System.err:     at android.os.MessageQueue.next(MessageQueue.java:326)
   15:57:02.265   W/System.err:     at android.os.Looper.loop(Looper.java:160)
   15:57:02.266   W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6692)
   15:57:02.266   W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
   15:57:02.266   W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
   15:57:02.266   W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

   15:57:14.582   W/System.err: java.lang.Exception: Stack trace
   15:57:14.586   W/System.err:     at java.lang.Thread.dumpStack(Thread.java:1348)
   15:57:14.586   W/System.err:     at net.toughcoder.view.DemoEventView.dispatchTouchEvent(DemoEventView.java:24)
   15:57:14.586   W/System.err:     at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
   15:57:14.586   W/System.err:     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
   15:57:14.586   W/System.err:     at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
   15:57:14.587   W/System.err:     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
   15:57:14.587   W/System.err:     at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
   15:57:14.587   W/System.err:     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
   15:57:14.587   W/System.err:     at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
   15:57:14.587   W/System.err:     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
   15:57:14.587   W/System.err:     at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:440)
   15:57:14.588   W/System.err:     at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1830)
   15:57:14.588   W/System.err:     at android.app.Activity.dispatchTouchEvent(Activity.java:3400)
   15:57:14.588   W/System.err:     at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:398)
   15:57:14.588   W/System.err:     at android.view.View.dispatchPointerEvent(View.java:12753)
   15:57:14.588   W/System.err:     at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5122)
   15:57:14.588   W/System.err:     at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4925)
   15:57:14.588   W/System.err:     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
   15:57:14.588   W/System.err:     at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4495)
   15:57:14.589   W/System.err:     at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4461)
   15:57:14.589   W/System.err:     at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4601)
   15:57:14.589   W/System.err:     at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4469)
   15:57:14.589   W/System.err:     at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4658)
   15:57:14.589   W/System.err:     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
   15:57:14.589   W/System.err:     at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4495)
   15:57:14.589   W/System.err:     at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4461)
   15:57:14.589   W/System.err:     at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4469)
   15:57:14.590   W/System.err:     at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
   15:57:14.590   W/System.err:     at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7117)
   15:57:14.590   W/System.err:     at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7086)
   15:57:14.590   W/System.err:     at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7047)
   15:57:14.590   W/System.err:     at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7220)
   15:57:14.590   W/System.err:     at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:187)
   15:57:14.590   W/System.err:     at android.os.MessageQueue.nativePollOnce(Native Method)
   15:57:14.590   W/System.err:     at android.os.MessageQueue.next(MessageQueue.java:326)
   15:57:14.591   W/System.err:     at android.os.Looper.loop(Looper.java:160)
   15:57:14.591   W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6692)
   15:57:14.591   W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
   15:57:14.591   W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
   15:57:14.591   W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Therefore, from the application point of view, the first to receive events is Activity or Dialog, the top-level component that holds Window, so if you want to intercept all events from the window level, then Activity will be the best option, code example:

   @Override
   public boolean dispatchKeyEvent(KeyEvent event) {
        if (want to intercept all key events) {
              return true;
        }
        return super.dispatchKeyEvent(event);
    }

     @Override
   public boolean dispatchTouchEvent(MotionEvent event) {
        if (want to intercept all touch events) {
              return true;
        }
        return super.dispatchTouchEvent(event);
    }

The above two methods are the first methods to receive events before the view tree, and they are the best place to intercept in the component, which is the pioneer from the front. However, if you want to deal with unhandled events in the view tree, you need to process them in onKeyUp(int keyCode, KeyEvent event) and onKeyDown(int keyCode, KeyEvent event) and onTouchEvent(MotionEvent event), which is equivalent to breaking.

Event Propagation

After the event arrives at the end of the application, the distribution process starts from the Activity. Its mechanism and process are like a small ball flowing everywhere. Each node receives an event object and returns a boolean. If it returns true, it means that the event is here. is consumed, the current event dissemination is terminated, and if it returns false, it means that the current node is not interested in this event, and the event continues to be disseminated.

The specific process is to go to the Activity (the first-level components such as Dialog) first, and then to the view tree. The same is true in the view tree, passing from the parent view to the child view one by one. This sequential process is determined by the entire determined by the system architecture.

An event is a stream of data

The distribution process of the events mentioned above can be seen as a ball flowing, which is true from the perspective of processing a single event. But it is even more so from the perspective of the whole event, because events are usually like electronic signals, starting from the source (such as touch screen, hardware, etc.), and the whole process of dispatching a series of event objects with a certain time interval. A ball is sent out every 1 second, such a data stream.

Having said so much, it is relatively simple to actually do it. Although it is a data stream, each stream has a start and end mark, so it is not difficult to deal with. For example, KeyEvent starts with onKeyDown, then onKeyUp, and processes in these two to complete the processing of the KeyEvent stream.

The MotionEvent is a little more complicated. For a MotionEvent stream, the system will continuously call back onTouchEvent until the end, judged by MotionEvent#getAction(), from ACTION_DOWN to ACTION_MOVE to ACTION_UP or ACTION_CANCEL.

Note : Because the handling of KeyEvent is relatively simple, the rest of the section will focus on MotionEvent.

The distribution process of Touch Event

After the event is generated, it will be passed to Activity#dispatchTouchEvent. If it is not intercepted, it will be passed to Window, and Window will be passed to ViewRootImpl for processing. After the view tree is processed, it will be passed to Activity#onTouchEvent:

 public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

This method can clearly see the order of the vanguard and post-break and the view tree in the event dispatch flow.

Let's focus on the process of dispatching events in the Window (view tree). In fact, it is enough to focus on View#dispatchTouchEvent and ViewGroup#dispatchTouchEvent. It should be noted that the event dispatch process is different from the processing process. The dispatch comes first and the processing comes later. So if you look at the dispatch of events, you need to look at the dispatch at the beginning. method, and the processing depends on the beginning of on.

View's dispatch is simpler, because it provides a default implementation, and View is a leaf in the view tree, so its dispatch is actually an end point, so what it does is to see if there is an OnTouchListener , if there is, call its onTouch, and then call onTouchEvent to process the event, and it's over. It can also be seen from here that OnTouchListener is ahead of the onTouchEvent method.

As for ViewGroup, it is relatively complicated, because it has to manage sub-Views, dispatch events to sub-Views, and also handle interception. Its logic is roughly: first check whether you want to intercept onInterceptTouchEvent, return true to indicate that you want to intercept, false will not intercept, if you want to intercept, call your own onTouchEvent to handle the event, and then terminate the dispatch (the real logic is slightly more complicated, different The ACTION processing logic is different).

Let’s focus on how ViewGroup dispatches events to child Views. When not intercepted, this is more conventional. Events will be dispatched to child Views. Let’s take a look at the process: First, buildTouchDispatchChildList will be used to buildTouchDispatchChildList Select the order of the sub-Views. This method is to sort the sub-Views according to the process of event dispatch. This order is the order that the user sees. It will be sorted by the Z axis (from the inside to the outside of the screen), and the rendering (draw) Order, after all, from the user's point of view, the first one to be clicked must be the one with the largest Z axis (closest to the user), and the first one to be drawn (not blocked). Then in this order, call the dispatchTouchEvent on it according to each sub-View, and pass the event to the sub-View. Of course, this is also an event that is flowing. Once the event is consumed, it will stop dispatching.

From this process, view tree event dispatching is a depth-first process, so the depth of the view tree not only affects rendering performance, but even event processing is slower than flat ones.

Touch Event event processing method

The processing of events is various methods starting with on such as onTouchEvent, or various listeners (OnClickListener, OnTouchListener). Generally speaking, it is enough to set various listeners, but if you want to customize something, you can directly override the onTouchEvent method. I won’t go into details here, and there are too many tutorials.

The difference between listener and direct Override parent class method

It should be noted that if you want to override, you must customize the View, so this is a more "hack-like" method. It is only necessary to customize the View and the conventional various listeners cannot meet the requirements. For example, Implement various custom gestures, etc.

The biggest advantage of the listener is that it is very simple and convenient, and has good isolation. The trigger and result of the event are isolated. If you want to deal with the event, you just need to implement an interface. As for the trigger of the event condition, you don’t need to worry about it. Any object can implement it. interface to handle events without having to non-desubclass (inherit) the View object.

It should also be noted that OnTouchListener occurs earlier than onTouchEvent, while conventional gesture callback interfaces (such as OnClickListener and onLongClickListner) are triggered in onTouchEvent. Therefore, OnTouchListener is actually a more low-level "hacking" interface. Generally, this interface needs to be implemented when custom recognition gestures are required.

prevent click through

Sometimes there will be some click-through problems, such as writing a layout with several Buttons and TextViews in it, but when you click on a blank area outside the main content, the Button on the next layer of the page receives an event , such as triggering its onClick event. This problem is more common when using stacked Fragments. In fact, you can see the solution from View#onTouchEvent. If a View is clickable, it will consume the event, and if clickable is false, it will continue to pass.

The reason for the penetration is the blank area. There is only one root layout of this layer page, which is usually a ViewGroup, and most of the ViewGroup default clickable is false, so the event will continue to be passed to the view tree until it is consumed.

The easiest solution to this type of problem is to set View to clickable="true", which can be set in the layout file.

Basic Gesture Recognition

Basic gesture recognition refers to the classification of some simple operations for touch-based operations, such as lightly touching the screen and immediately removing it, which is regarded as a click (click or press, or tap), and long-pressing the screen as long click Or called long press, as well as sliding, double-clicking and so on. Gesture recognition is a set of logic algorithms used to determine which kind of operation the user is currently performing, and then trigger corresponding processing logic to give feedback on the user's operation. So much nonsense, let's see how to do it.

The basic gestures in the Android GUI system are click and long click. There are two ways to recognize these basic gestures. One is to set the interface to call back to View, that is, implement an OnClickListener, and then set this object to View#setOnClickListener (long press is OnLongClickListener and View#setOnLongClickListener; the other method is for Inside the view tree, such as subclassing (inheriting) a View object, and then overriding the corresponding method.

Note : It is also convenient to use onclick to specify the gesture callback method in the layout xml file, but its essence is the same as setting an OnClickListener.

If clicking and long pressing cannot meet the operation requirements, a slightly more complicated basic gesture recognition object is needed to help, that is, GestureDetector, which is connected to the View by separating the interface. The source of the MotionEvent event is enough. The method used is not complicated, just set an OnTouchListener or subclass View and override the onTouch method, get the MotionEvent object from it, and then insert the MotionEvent to a GestureDetecotor object, and it’s over, GestureDetector will call back the corresponding gesture processing you are interested in Callback method, through OnGestureListener object. Because OnGestureListener is an interface, but if you are only interested in a few gesture callback methods and don't want to write all the methods (even if it is an empty implementation), you can subclass SimpleOnGestureListener, which is a class that implements OnGestureListener For all methods, we only need to override the methods we are interested in.

One thing that needs special attention is that when you use GestureDetector, its sequence with the regular onClick or onLongClick, or conflict handling. Based on the principle of consistency, if GestureDetector is used, it means that you want to control the event processing by yourself, then you should not set onClick or onLongClick anymore. But what happens if you do it by accident? This requires finding the answer from View's event handling process. OnTouchListener is called in View#dispatchTouchEvent, this is before View#onTouchEvent, and OnClickListener and OnLongClickListener are called in View#onTouchEvent. So, the sequence is this:

  1. If you use OnTouchListener to get the MotionEvent, then your OnGestureListener's callback method is called first, before all other callbacks.
  2. If it is the event obtained by the override View#onTouchEvent method, it depends on the order in which you call super#onTouchEvent. If you call super before, then your gesture listener will execute first. In fact, the normal way of writing override must first write its own logic and then call super, or not call super at all. This is the most orthodox subclass override parent class posture.

From this, it can be concluded that if GestureDetector is used, then your gesture listener must be executed first.

Trigger timing of onClick and onLongClick

Let's look at two other interesting questions. When is onClick triggered? It can be seen from the View#onTouchEvent method that it is triggered when ACTION_UP is triggered. If it has not triggered long click, the long click will start timing after ACTION_DOWN after the event starts, and it will be triggered after a certain time interval. It is not counted as follow-up. event type.

The overall process is like this. In View#onTouchEvent, event types are processed. Start timing in ACTION_DOWN, and continue timing in ACTION_MOVE later. If the long press standard is reached, long click will be triggered. In ACTION_UP that ends normally, check if there is any If the long press standard is met, long click will be triggered if there is one, and on click will be triggered if there is no.

System Threshold Definition

Key thresholds such as the duration of long press, the minimum distance of sliding, the minimum distance of stretching, etc. are defined by the system suggestion. These values ​​are all in ViewConfiguration. It is generally recommended to use the system definition directly, unless there is a real one. special needs.

Android study notes

Android Performance Optimization: https://qr18.cn/FVlo89
Android Vehicle: https://qr18.cn/F05ZCM
Android Reverse Security Study Notes: https://qr18.cn/CQ5TcL
Android Framework Principles: https://qr18.cn/AQpN4J
Android Audio and Video: https://qr18.cn/Ei3VPD
Jetpack (including Compose): https://qr18.cn/A0gajp
Kotlin: https://qr18.cn/CdjtAF
Gradle: https://qr18.cn/DzrmMB
OkHttp Source Code Analysis Notes: https://qr18.cn/Cw0pBD
Flutter: https://qr18.cn/DIvKma
Android Eight Knowledge Body: https://qr18.cn/CyxarU
Android Core Notes: https://qr21.cn/CaZQLo
Android Past Interview Questions: https://qr18.cn/CKV8OZ
2023 Latest Android Interview Question Collection: https://qr18.cn/CgxrRy
Android Vehicle Development Job Interview Exercises: https://qr18.cn/FTlyCJ
Audio and Video Interview Questions:https://qr18.cn/AcV6Ap

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/131534630