【Android Framework Series】Chapter 8 Event Distribution Do you really understand it?

1 Basic understanding of event distribution

1.1 What does "event" mean in event distribution

insert image description here

1.2 Points involved in event processing

insert image description here

1.3 Three processes of Android event processing

In Android, Touchthe distribution of events 服务端and 应用端. WindowManagerService(借助InputManagerService)负责It is collected and distributed on the server side , and ViewRootImpl(内部有一个mView变量指向View树的根,负责控制View树的UI绘制和事件消息的分发)负责distributed on the application side.
When the input device is available, such as a touch screen, Linuxthe kernel will /dev/inputcreate a corresponding device node in it, and the original information generated by the input event will be stored Linuxin the kernel 输入子系统采集, and the original information will Kernel Space的驱动层be passed to all the devices under User Space的设备节点
IMSmonitoring. /dev/inputNode, when the device node has data, it will process the data and find a suitable one Window, and dispatch the input event to him
insert image description here

1.4 The nature of the event

The click event ( MotionEvent) is passed to a View and finally processed, that is, when a click event occurs, the system needs to pass the event to a specific View for processing. The process of passing this event is the distribution process.

adb shell getevent
adb shell input event 4
adb shell getevent -t -l

1.5 Linux - Posix function understanding

ePoll

It is recognized as the best multi-channel I/O readiness notification method under Linux2.6. epollThere are only epoll_create, epoll_ctl, epoll_wait3 system calls.

epoll_create

When the handle is createdepoll , it will occupy a fdvalue. If you look at linuxit below /proc/进程id/fd/, you can see this fd, so after using epollit , you must call close()close, otherwise it may be fdexhausted.

epoll_ctl

epollThe event registration function is different from select()telling the kernel what type of event to listen to when listening to the event, but to register the event type to be listened to here first.

epoll_wait

Collect epollthe events that have been sent in the monitored events. The parameter eventsis an array of allocated epoll_eventstructures, epollwhich will assign the events that occurred to eventsthe array (events cannot be a null pointer, the kernel is only responsible for copying data to the events array, and will not help us allocate memory in user mode ). maxeventsTell the kernel eventshow big this is, maxeventsthe value of this cannot be greater than epoll_create()when it was created size, and the parameter timeoutis the timeout period (in milliseconds, 0 will return immediately, -1 will be uncertain, and it is also said that it is permanently blocked). If the function call is successful, it returns the number of prepared file descriptors on the corresponding I/O. If it returns 0, it means that it has timed out.
For specific reference documents

iNotify

It is a mechanism used by the kernel to notify user space programs of file system changes

// 每一个 inotify 实例对应一个独立的排序的队列
int fd = inotify_init ();
// fd 是 inotify_init() 返回的文件描述符,path 是被监视的目标的路径名(即文件名或目录名),
// mask 是事件掩码, 在头文件 linux/inotify.h 中定义了每一位代表的事件。
// 可以使用同样的方式来修改事件掩码,即改变希望被通知的inotify 事件。
// Wd 是 watch 描述符。
inotify_add_watch

Specific usage reference

socketpair

socketpair()Function used to create a pair of unnamed, interconnected sockets.
Specific use reference

fd

linuxIn the kernel, each process has an array of "open files" in the kernel, which stores pointers to file objects, and fd is the subscript of this array .

When we operate on a file, the system calls and passes fd to the kernel, and the kernel finds the file through fd and operates on the file.
Since it is an array subscript, the type of fd is int, < 0 is an illegal value, and >=0 is a legal value.
In Linux, the number of files that can be opened by a process is 1024 by default, and the range of fd is 0~1023. The maximum value can be changed by setting.
In Linux, fd with values ​​of 0, 1, and 2 represent standard input, standard output, and standard error output, respectively.

2 Event collection process

2.1 Process from Kernel to IMS

2.1.1 Flowchart

insert image description here

2.1.2 EventHub

2.1.2.1 INotify

INotifyIt is a file system change notification mechanism provided by Linux . What is a file system change? Creating, deleting, and reading and writing are all called changes. Use the following code to add a directory to INotify

int inotifyFd = inotify_init();
int wd = inotify_add_watch(inotifyFd, "/dev/input", IN_CREATE | IN_DELETE);

The above two lines of code will /dev/inputbe added to INotify, so that the plugging and unplugging of external input devices can be well detected. Unfortunately, INotifyafter discovering the file system changes, it will not take the initiative to tell others. It needs to actively read()read inotifyFdthe descriptor through the function to obtain the specific change information. That is to say, when you insert the mouse, INotifyyou immediately know the information, and update the information to inotifyFdthe object corresponding to the descriptor

2.1.2.2 Eventhub

EpollinotifyFdIt can be used to monitor the readable and writable status of multiple descriptors . What does it mean? For example, it is mentioned above that the plugging and unplugging of external input devices can be detected by INotify, and the information will be written into the object used by inotifyFd, but I have no way of knowing when INotify has captured this information. And Epollyou can monitor inotifyFdwhether the content of the corresponding object has changed. Once there is a change, it can be processed immediately. Usually, when you don’t monitor the change most of the time, you sleep a lot and stay in the
insert image description here
epoll_waitblocked state most of the time (this is very similar to the socket waiting for connection). Once the /dev/input/event0node If there is a change (that is, an input event is generated), epoll_waitit will be executed

2.1.3 IMS

2.1.3.1 InputReaderThread

InputReaderwill be read continuously EventHubin a loop原始输入事件

2.1.3.2 InputDispatcherThread

InputDispatcherSave WMSall the information in Window信息( WMSthe information of the window will be updated in real time InputDispatcher), so that InputDispatcherthe input event can be dispatched to the appropriateWindow

2.1.3.3 Overall Process Source Code Analysis

2.1.3.3.1 Start an IMS process by SystemServer, and then initialize each object

SystemServer.startOtherServices
insert image description here
InputManagerService.InputManagerService
insert image description here
insert image description here

2.1.3.3.2 Synchronization will be initialized in the native layer, and an InputManager object will be opened|

Open two threads: a Reader thread for
reading touch signals,
and a Dispatcher for forwarding information. EventHub
is responsible for collecting underlying signals . InputDispatcher
insert image description here

insert image description here

insert image description here

insert image description here

insert image description here

insert image description here

insert image description here

2.1.3.3.3 Start the above two threads through IMS.start

SystemServer.startOtherService
insert image description here
InputManagerService.start
insert image description here
com_android_server_input_InputManagerService.cpp
insert image description here
Inputmanager.cpp
insert image description here

2.1.3.3.4 InputRerader thread processing process

overall indication
insert image description here

2.1.3.3.4.1 Thread running

InputReader.cpp
insert image description here

2.1.3.3.4.2 Handle events, pay attention to the blocking problem of getEvent

InputReader.cpp

void InputReader::loopOnce() {
    
    
358    int32_t oldGeneration;
359    int32_t timeoutMillis;
360    bool inputDevicesChanged = false;
361    Vector<InputDeviceInfo> inputDevices;
362    {
    
     // acquire lock
363        AutoMutex _l(mLock);
364
365        oldGeneration = mGeneration;
366        timeoutMillis = -1;
367         //查看InputReader配置是否修改
368        uint32_t changes = mConfigurationChangesToRefresh;
369        if (changes) {
    
    
370            mConfigurationChangesToRefresh = 0;
371            timeoutMillis = 0;
372            refreshConfigurationLocked(changes);
373        } else if (mNextTimeout != LLONG_MAX) {
    
    
374            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
375            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
376        }
377    } // release lock
378      //从Eventhub中读取事件,这里注意因为之前将的EventHub.getEvent中有epoll_wait进行阻塞,若FD发生改变则执行后面代码
379    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
380
381    {
    
     // acquire lock
382        AutoMutex _l(mLock);
383        mReaderIsAliveCondition.broadcast();
384
385        if (count) {
    
    //处理事件
386            processEventsLocked(mEventBuffer, count);
387        }
388
389        if (mNextTimeout != LLONG_MAX) {
    
    
390            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
391            if (now >= mNextTimeout) {
    
    
392#if DEBUG_RAW_EVENTS
393                ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
394#endif
395                mNextTimeout = LLONG_MAX;
396                timeoutExpiredLocked(now);
397            }
398        }
399
400        if (oldGeneration != mGeneration) {
    
    
401            inputDevicesChanged = true;
402            getInputDevicesLocked(inputDevices);
403        }
404    } // release lock
405
406    // Send out a message that the describes the changed input devices.
407    if (inputDevicesChanged) {
    
    
408        mPolicy->notifyInputDevicesChanged(inputDevices);//发送一个消息,该消息描述已更改
409    }
410
411    // Flush queued events out to the listener.
412    // This must happen outside of the lock because the listener could potentially call
413    // back into the InputReader's methods, such as getScanCodeState, or become blocked
414    // on another thread similarly waiting to acquire the InputReader lock thereby
415    // resulting in a deadlock.  This situation is actually quite plausible because the
416    // listener is actually the input dispatcher, which calls into the window manager,
417    // which occasionally calls into the input reader.
418    mQueuedListener->flush();//将时间传递到InputDispatcher
419}
2.1.3.3.4.3 Downward derivative call, ProcessEventsForDeviceLocked is to process data flow, the following is for file status processing

processEventsLocked
insert image description here

2.1.3.3.4.4 Finally call Device->process

processEventsForDeviceLocked
insert image description here

2.1.3.3.4.5 Call

InputDevice::process
insert image description here

2.1.3.3.4.6 InputMapper::process

Note here that InputMapper has multiple subclasses.
We are looking for touch event related processing is to find TouchInputMapper
insert image description here

2.1.3.3.4.7 TouchInputManager::sync

insert image description here

2.1.3.3.4.8 Then the final data flow

1.processRawTouches(false / timeout /);
4345
2.cookAndDispatch
insert image description here
3.dispatchTouches
insert image description here
4.dispatchMotion
insert image description here
5. The actual acquisition of the current getListener is mQueuedListener = new QueuedInputListener(listener); example
insert image description here
6.InputLintener.cpp.QueuedInputListener::notifyMotion
Here we can actually Seeing it is adding data to a queue.
In fact, after we package the touch-related events, we add them to an ArgsQueue queue.
So far, we have added the data to the parameter queue.
insert image description here
7. At this time, we are returning After the above processEventsLocked call, I found the current mQueueListener->flush call
insert image description here
8.InputLintener.cpp.QueuedInputListener::flush

To traverse the entire mArgsQueuearray, the implementation subclasses of NotifyArgs in the input architecture mainly include the following categories

NotifyConfigurationChangedArgs
NotifyKeyArgs
NotifyMotionArgs 通知motion事件
NotifySwitchArgs
NotifyDeviceResetArgs

insert image description here

9.
The listener here in NotifyMotionArgs can be seen to be InputDispatcher according to the traceability.
insert image description here
insert image description here
Therefore, in summary, the notifyMotion in InputDispatcher is called, and the information transmission to InputDispatcher is completed here. 10. Execute the wake-up operation InputDispatcher.cpp
insert image description here
in notifyMotion

insert image description here
insert image description here

2.1.3.3.5 InputDispatcher thread processing process

Overall process schematic
insert image description here

2.1.3.3.5.1 InputDispatcher.cpp.threadLoop()

4623

2.1.3.3.5.2 InputDispatcher.cpp.dispatchOnce()

pollOnceHere is waiting to be woken up, enter epoll_waitthe waiting state, and exit the waiting state when any of the following situations occur:
callback: wake up through the callback method;
timeout: arrive at nextWakeupTimethe time, wake up after timeout;
wake: actively call Looperthe wake()method;
insert image description here

2.1.3.3.5.3 dispatchOnceInnerLocked

294
insert image description here

2.1.3.3.5.4 dispatchMotionLocked

Here are two lines,
1.findTouchedWindowTargetLocked is responsible for finding the Window to be distributed 2.dispatchEventLocked is
responsible for the specific distribution target
864
insert image description here
1.findTouchedWindowTargetLocked is responsible for finding the Window that can be distributed , if this cannot continue to process events, it means that the main thread of this is occupied by some time-consuming operations, this method is mainly to determine whether the current ANR occurs, if not, then process the following . The main purpose of addWindowTargetLocked here is to add to
1166
insert image description here
insert image description here


forceground windowwindowwindowhandleTargetsNotReadyLockedwindowaddWindowTargetLocked
insert image description here

inputChannelInputTarget
insert image description here

2. dispatchEventLocked is responsible for the specific distribution target
1. dispatchEventLocked
958

2.prepareDispatchCycleLocked
1837

3. enqueueDispatchEntriesLocked
The main function of this method:
to decide whether to join the outboundQueue queue according to the dispatchMode; to
generate the DispatchEntry event according to the EventEntry;
to add the dispatchEntry to the outbound queue of the connection.
Executing here is actually equivalent to doing a handling work, will After the event in mInboundQueue in InputDispatcher is taken out, after finding the target window, encapsulate dispatchEntry and add it to the outbound queue of connection.
insert image description here

4.startDispatchCycleLocked
The main function of startDispatchCycleLocked: Take out the event from the outboundQueue and put it back into the waitQueue queue

startDispatchCycleLocked trigger timing: when connection.outboundQueue is equal to empty at first, after being processed by enqueueDispatchEntryLocked, outboundQueue is not equal to empty.

The main function of startDispatchCycleLocked: take out the event from the outboundQueue and put it back into the waitQueue queue

When the publishMotionEvent execution result status is not equal to OK:

insert image description here
insert image description here
insert image description here
5. So far, the publishMotionEvent method of the inputPublisher of the connection is called to distribute and consume the event.
InputTransport.cpp
insert image description here
insert image description here

2.2 Process from IMS to WMS

2.2.1 InputChannel

2.2.1.1 Concept

InputReaderThreadAnd the user process InputDispatcherThreadrunning in SystemServerthe process
is not in the same process as it, so there must be an inter-process communication mechanism in it. The
InputChannelmain core purpose is to communicate with the user process. This InputChannelis WMS管理triggered by build build

2.2.1.2 Model schematic

insert image description here

2.2.1.3 Create InputChannel process

1. The call that will be triggered in the function ViewRootImplin the note here is that an object is constructed at the java level before proceeding. 2. Through the progress in the middle. This is actually to build a communication mechanism at the native level. Considering performance issues, it is generally Rely on the underlying communication mechanism of linux 3. OpenInputChannel construction and registration 4.InputChannel.openInputChannelPair actually calls the native layer to create a set of communication Note that this function can be seen clearly in InputTransport.cpp, and two groups are built socket communicationsetViewsession.addToDispalyWMSaddWindow
addTodisplayInputChannel
insert image description here
insert image description here
addWindowWindowStateopenInputChannel


insert image description here


insert image description here

insert image description here

Linux implements a socketpaircall to implement the above functions of reading and writing in the same file descriptor (this call is also POSIXpart of the specification. This system call can create a pair of connected (UNIX族)无名socket. In Linux, it is completely possible to put this pair Used socketas the returned file descriptor, the only difference is that any one of the pair of pipefile descriptors is readable and writable.
insert image description here
The channel is registered in the InputDispatcher , and
finally the InputDispatcher.cpp.registerInputChannel function is called.
At this point, the current InputChannelobject is passed in InputDispatcher, and a certain encapsulation is carried out, and the window handle is passed in at the same time.
insert image description here
insert image description here
insert image description here

2.2.1.4 Enable the monitoring of input events to receive events

1. setViewIn the middle, after we created it InputChannel, we started to InputChannellisten to the input event
in ViewRootImpl.setview
846
. Here is the structure called to the parent class.
insert image description here
2. Build a listener
android_view_InputEventReceiver.cpp in the native layer to listen to the event
339
insert image description here
insert image description here
. The operation performed by this method is Adding epollmonitoring to the passed fd Looperwill call the method cyclically pollOnce, and pollOncethe core implementation of the method is pollInner.
The general implementation content is to wait for the arrival of the message. When a message arrives, do some judgment processing according to the message type, and then call its related callback.
We are currently socketmonitoring the opening. When data arrives, we will execute the corresponding callback. The callback here InputChannelis to call NativeInputEventReceiver的handleEventmethod
insert image description here
3. After the message is received, the callback
android_view_InputEventReceiver.cpp::handleEvent
insert image description here
android_view_InputEventReceiver.cpp::consumeEvents
insert image description here
consume
insert image description here
android_view_InputEventReceiver.cpp::consumeEvents
After the above message is received consume, consumeEventit will return to the middle after execution. Judgment processing based on the read data, and then up callto the Java function
insert image description here

2.2.1.5 Call back the upper layer JAVA function

2.3 The process of transferring from WMS to ViewRootImpl

1.InputEventReceiver.dispatchInputEvent
The InputEvent called here is sent to the subclass, because the specific implementation class isWindowInputEventReceiver
insert image description here

2.ViewRootImpl.WindowInputEventReceiver.onInputEvent
8182

3.ViewRootImpl.QueuedInputEvent.enqueueInputEvent
7992

4.ViewRootImpl.QueuedInputEvent.doProcessInputEvents
8031

5.ViewRootImpl.QueuedInputEvent.deliverInputEvent
8080

6. In ViewRootImpl, there are a series of similar InputStage(输入事件舞台)concepts,
each of which InputStagecan handle a certain type of event,
and the subclasses are: AsyncInputStage, ViewPreImeInputStage, ViewPostImeInputStageetc.
For the click event, ViewPostImeInputStageit can be handled.
ViewPostImeInputStageIn, there is a processPointerEventmethod,
which will be mViewcalled dispatchPointerEvent, Note that here mViewis actuallyDecorView

insert image description here
insert image description here
Note that the subclass must be called here. onProcess
insert image description here
insert image description here
insert image description here
insert image description here
Remember who is the View here? ? Here you can see the class that is DecorView!
insert image description here
finally called , which is called here, but considering the implementation class here , so the method of the subclass is called Here you can see that it is set in the call to one , which is the current ofViewdispatchPointerEventdispatchTouchEvent
DecView
insert image description here
windowcallback
insert image description here
callbackActivity.attach()activity
insert image description here

So what is called here ActivityisdispatchTouchEvent
insert image description here

3 Event distribution process

3.1 Chain of Responsibility Model

The chain of responsibility mode is a behavioral mode that creates a receiver object chain for the request. This avoids the situation where a request links multiple receivers. External decoupling. Similar to a one-way linked list structure

3.1.1 Advantages

  1. Reduce coupling . It decouples the sender and receiver of a request.
  2. Objects are simplified . So that the object does not need to know the structure of the chain.
  3. Enhanced flexibility in assigning responsibilities to objects . By changing the members in the chain or mobilizing their order, it is allowed to dynamically add or delete responsibilities.
  4. It is convenient to add new request handler classes .

3.1.2 Disadvantages

1. There is no guarantee that the request will be received.
2. The system performance will be affected to a certain extent , and it is not convenient to debug the code, which may cause circular calls.
3. It may not be easy to observe the characteristics of the runtime, which hinders debugging

3.1.3 The Android event distribution mechanism is the most typical application of the chain of responsibility model:

dispatchTouchEventIn the chain of responsibility, the event
onInterceptTouchEventis handed over to the next level for processing. In the chain of responsibility, the method of handling the transaction itself
onTouchEventis the event chain reported by the event in the chain of responsibility.

3.2 Which objects are passed between eventsinsert image description here

insert image description here

3.3 Event distribution sequence

insert image description here

3.4 What methods are used to coordinate the event distribution process?

insert image description here

3.5 Explanation on U-chain and L-chain in event distribution and consumption

3.5.1 Event distribution process

insert image description here
Detailed process of event distribution mechanism

super: call the parent class method
true: consume the event, that is, the event will not continue to pass down
false: do not consume the event, and the event will not continue to pass down / hand it over to the parent control onTouchEvent() for processing

3.5.1.1 Activity

ActivityThe thing to do is to forward it to the lower layer. In the case of no consumption, the last Activityreason is wrap up the bottom line

3.5.1.2 ViewGroup

things to do

  1. Whether to intercept at the container level
  2. Whether the time should be distributed, and the cancellation event does not need to be consumed
  3. Before passing sub- Viewconsumption, it depends on whether it is valid and which one is clicked Viewon
  4. So it is necessary to traverse all sub View-determining whether it exists

Source code analysis

public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
        //验证事件是否连续
        if (mInputEventConsistencyVerifier != null) {
    
    
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        //这个变量用于记录事件是否被处理完
        boolean handled = false;
        //过滤掉一些不合法的事件:当前的View的窗口被遮挡了。
        if (onFilterTouchEventForSecurity(ev)) {
    
    
            //如果事件发生的View在的窗口,没有被遮挡
            final int action = ev.getAction();
            //重置前面为0 ,只留下后八位,用于判断相等时候,可以提高性能。
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            //判断是不是Down事件,如果是的话,就要做初始化操作
            if (actionMasked == MotionEvent.ACTION_DOWN) {
    
    
                //如果是down事件,就要清空掉之前的状态,比如,重置手势判断什么的。
                //比如,之前正在判断是不是一个单点的滑动,但是第二个down来了,就表示,不可能是单点的滑动,要重新开始判断触摸的手势
                //清空掉mFirstTouchTarget
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
 
 
            //检查是否拦截事件
            final boolean intercepted;
            //如果当前是Down事件,或者已经有处理Touch事件的目标了
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
    
    
                //判断允不允许这个View拦截
                //使用与运算作为判断,可以让我们在flag中,存储好几个标志
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //如果说允许拦截事件
                if (!disallowIntercept) {
    
    
                    //确定是不是拦截了
                    intercepted = onInterceptTouchEvent(ev);
                    //重新恢复Action,以免action在上面的步骤被人为地改变了
                    ev.setAction(action); // restore action in case it was changed
                } else {
    
    
                    intercepted = false;
                }
            } else {
    
    
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                //如果说,事件已经初始化过了,并且没有子View被分配处理,那么就说明,这个ViewGroup已经拦截了这个事件
                intercepted = true;
            }
 
            // Check for cancelation.
            //如果viewFlag被设置了PFLAG_CANCEL_NEXT_UP_EVENT ,那么就表示,下一步应该是Cancel事件
            //或者如果当前的Action为取消,那么当前事件应该就是取消了。
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
 
            // Update list of touch targets for pointer down, if needed.
            //如果需要(不是取消,也没有被拦截)的话,那么在触摸down事件的时候更新触摸目标列表
            //split代表,当前的ViewGroup是不是支持分割MotionEvent到不同的View当中
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            //新的触摸对象,
            TouchTarget newTouchTarget = null;
            //是否把事件分配给了新的触摸
            boolean alreadyDispatchedToNewTouchTarget = false;
            //事件不是取消事件,也没有拦截那么就要判断
            if (!canceled && !intercepted) {
    
    
                //如果是个全新的Down事件
                //或者是有新的触摸点
                //或者是光标来回移动事件(不太明白什么时候发生)
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
    
                    //这个事件的索引,也就是第几个事件,如果是down事件就是0
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    //获取分配的ID的bit数量
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;
 
                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    //清理之前触摸这个指针标识,以防他们的目标变得不同步。
                    removePointersFromTouchTargets(idBitsToAssign);
 
                    final int childrenCount = mChildrenCount;
                    //如果新的触摸对象为null(这个不是铁定的吗)并且当前ViewGroup有子元素
                    if (newTouchTarget == null && childrenCount != 0) {
    
    
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        //下面所做的工作,就是找到可以接收这个事件的子元素
                        final View[] children = mChildren;
                        //是否使用自定义的顺序来添加控件
                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        for (int i = childrenCount - 1; i >= 0; i--) {
    
    
                            //如果是用了自定义的顺序来添加控件,那么绘制的View的顺序和mChildren的顺序是不一样的
                            //所以要根据getChildDrawingOrder取出真正的绘制的View
                            //自定义的绘制,可能第一个会画到第三个,和第四个,第二个画到第一个,这样里面的内容和Children是不一样的
                            final int childIndex = customOrder ?
                                    getChildDrawingOrder(childrenCount, i) : i;
                            final View child = children[childIndex];
                            //如果child不可以接收这个触摸的事件,或者触摸事件发生的位置不在这个View的范围内
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
    
    
                                continue;
                            }
                            //获取新的触摸对象,如果当前的子View在之前的触摸目标的列表当中就返回touchTarget
                            //子View不在之前的触摸目标列表那么就返回null
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
    
    
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                //如果新的触摸目标对象不为空,那么就把这个触摸的ID赋予它,这样子,
                                //这个触摸的目标对象的id就含有了好几个pointer的ID了
 
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                            //如果子View不在之前的触摸目标列表中,先重置childView的标志,去除掉CACEL的标志
                            resetCancelNextUpFlag(child);
                            //调用子View的dispatchTouchEvent,并且把pointer的id 赋予进去
                            //如果说,子View接收并且处理了这个事件,那么就更新上一次触摸事件的信息,
                            //并且为创建一个新的触摸目标对象,并且绑定这个子View和Pointer的ID
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    
    
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = childIndex;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //这里给mFirstTouchTarget赋值
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }
                    //如果newTouchTarget为null,就代表,这个事件没有找到子View去处理它,
                    //那么,如果之前已经有了触摸对象(比如,我点了一张图,另一个手指在外面图的外面点下去)
                    //那么就把这个之前那个触摸目标定为第一个触摸对象,并且把这个触摸(pointer)分配给最近添加的触摸目标
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
    
    
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
    
    
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
 
            // Dispatch to touch targets.
            //如果没有触摸目标
            if (mFirstTouchTarget == null) {
    
    
                // No touch targets so treat this as an ordinary view.
                //那么就表示我们要自己在这个ViewGroup处理这个触摸事件了
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
    
    
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                //遍历TouchTargt树.分发事件,如果我们已经分发给了新的TouchTarget那么我们就不再分发给newTouchTarget
                while (target != null) {
    
    
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
    
    
                        handled = true;
                    } else {
    
    
                        //是否让child取消处理事件,如果为true,就会分发给child一个ACTION_CANCEL事件
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //派发事件
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
    
    
                            handled = true;
                        }
                        //cancelChild也就是说,派发给了当前child一个ACTION_CANCEL事件,
                        //那么就移除这个child
                        if (cancelChild) {
    
    
                            //没有父节点,也就是当前是第一个TouchTarget
                            //那么就把头去掉
                            if (predecessor == null) {
    
    
                                mFirstTouchTarget = next;
                            } else {
    
    
                                //把下一个赋予父节点的上一个,这样当前节点就被丢弃了
                                predecessor.next = next;
                            }
                            //回收内存
                            target.recycle();
                            //把下一个赋予现在
                            target = next;
                            //下面的两行不执行了,因为我们已经做了链表的操作了。
                            //主要是我们不能执行predecessor=target,因为删除本节点的话,父节点还是父节点
                            continue;
                        }
                    }
                    //如果没有删除本节点,那么下一轮父节点就是当前节点,下一个节点也是下一轮的当前节点
                    predecessor = target;
                    target = next;
                }
            }
 
            // Update list of touch targets for pointer up or cancel, if needed.
            //遇到了取消事件、或者是单点触摸下情况下手指离开,我们就要更新触摸的状态
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
    
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
    
    
                //如果是多点触摸下的手指抬起事件,就要根据idBit从TouchTarget中移除掉对应的Pointer(触摸点)
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }
 
        if (!handled && mInputEventConsistencyVerifier != null) {
    
    
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

Mainly did these three things:

  1. Check if the event is intercepted
  2. Check subviews and pass events
  3. Handle events yourself

Overall process:

  1. DOWNevent, by polling the current ViewGrouplowerView
  2. Synchronously judge whether it is Viewwithin the range of this rectangle
  3. If it is, then select this Viewto dispatchforward, he ViewGroupwill continue to forward the lower layer, if it is Viewdirect consumption processing!
  4. MOVEType, because of its characteristics, the amount is large, and it is not suitable to use polling
  5. Therefore, DOWNwhen you come down, directly record the current one View, and then forward this directly by defaultView
  6. If the finger draws the current View, the event information ActionIDwill be changed, after changing the ID, targetthe change will be made! UP\ CANCELwill reset

3.5.1.3 View

Source code analysis

/**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     * 将屏幕的按压事件传递给目标view,或者当前view即目标view
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
    
    
        //最前面这一段就是判断当前事件是否能获得焦点,
        // 如果不能获得焦点或者不存在一个View,那我们就直接返回False跳出循环
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
    
    
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
    
    
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }
        //设置返回的默认值
        boolean result = false;
        //这段是系统调试方面,可以直接忽略
        if (mInputEventConsistencyVerifier != null) {
    
    
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        //Android用一个32位的整型值表示一次TouchEvent事件,低8位表示touch事件的具体动作,比如按下,抬起,滑动,还有多点触控时的按下,抬起,这个和单点是区分开的,下面看具体的方法:
        //1 getAction:触摸动作的原始32位信息,包括事件的动作,触控点信息
        //2 getActionMasked:触摸的动作,按下,抬起,滑动,多点按下,多点抬起
        //3 getActionIndex:触控点信息
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
    
    
            //当我们手指按到View上时,其他的依赖滑动都要先停下
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }
        //过滤掉一些不合法的事件,比如当前的View的窗口被遮挡了。
        if (onFilterTouchEventForSecurity(event)) {
    
    
            //ListenerInfo 是view的一个内部类 里面有各种各样的listener,
            // 例如OnClickListener,OnLongClickListener,OnTouchListener等等
            ListenerInfo li = mListenerInfo;

            //首先判断如果监听li对象!=null 且我们通过setOnTouchListener设置了监听,
            // 即是否有实现OnTouchListener,
            // 如果有实现就判断当前的view状态是不是ENABLED,
            // 如果实现的OnTouchListener的onTouch中返回true,并处理事件,则
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
    
    
                //如果满足这些条件那么返回true,这个事件就在此处理意味着这个View需要事件分发
                result = true;
            }
            //如果上一段判断的条件没有满足(没有在代码里面setOnTouchListener的话),
            // 就判断View自身的onTouchEvent方法有没有处理,没有处理最后返回false,处理了返回true;
            //也就是前面的判断优先级更高

            if (!result && onTouchEvent(event)) {
    
    
                result = true;
            }
        }
        系统调试分析相关,没有影响
        if (!result && mInputEventConsistencyVerifier != null) {
    
    
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        //如果这是手势的结尾,则在嵌套滚动后清理
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
    
    
            stopNestedScroll();
        }

        return result;
    }

listenerThe one that is called first onTouch, and onTouchEventthe one that is called is based on onTouchthe result.
onTouchThe priority of the one that is used is higher than onTouchEventthe onTouchone that can be controlled by the return value onTouchEvent.

3.5.2 Event consumption process

3.5.3 U-shaped model

Simple understanding: events are passed from top to bottom and consumed from bottom to top (no consumption)

3.5.4 L-shaped model

Simple understanding: events are passed from top to bottom, and then intercepted (with consumption)

4 Summary

Finally, to sum up, event distribution means that events collect data from the linux layer through the driver, and the bottom layer uses epoll and inotify to pass it out. The FrameWork layer reads through InputReaderThread and distributes it to WMS through InputDispatcherThread. WMS passes the event to Activity, the upper layer Activity, ViewGroup, Distribution and consumption of events between Views .

specific process:

  1. The event signal is the physical file storage data, location:dev/input
  2. linuxThere are related file monitoring APIs, which use inotify(能监控文件变化产生FD) andepoll(可以监控FD,以此配合完成文件的监控与监听)
  3. AndroidWrote two threads to handle dev/inputthe following signals ( InputReaderThreadand InputDispathcerThread)
  4. EventHubA special object is written , which is used to monitor the next inotify+epoll!dev/input
  5. Put this object into InputReaderThreadit for execution, rotation training getEvent(), which epoll_waitis equivalent to wait-notifythe mechanism, the trigger point of the wake-up is that /dev/inputthe file under it is changed, and the file is pushed by the driver to push the data
  6. InputReaderThread/dev/inputThe following data will be extracted, packaged, and then handed over toInputDispathcerThread
  7. InputDispathcerThreadGive the final choice to the correspondingViewRootImpl(Window)
  8. The communication mechanism in the middle socketpairis carried out through a group of people on both sidessocketpair
  9. Then monitor the connected files in ViewRootImplthe middle , once the signal can be finally received by the upper layer!Channeltouch

Guess you like

Origin blog.csdn.net/u010687761/article/details/131914632