1 Basic understanding of event distribution
1.1 What does "event" mean in event distribution
1.2 Points involved in event processing
1.3 Three processes of Android event processing
In Android, Touch
the 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, Linux
the kernel will /dev/input
create a corresponding device node in it, and the original information generated by the input event will be stored Linux
in the kernel 输入子系统采集
, and the original information will Kernel Space的驱动层
be passed to all the devices under User Space的设备节点
IMS
monitoring. /dev/input
Node, when the device node has data, it will process the data and find a suitable one Window
, and dispatch the input event to him
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. epoll
There are only epoll_create
, epoll_ctl
, epoll_wait
3 system calls.
epoll_create
When the handle is createdepoll
, it will occupy a fd
value. If you look at linux
it below /proc/进程id/fd/
, you can see this fd
, so after using epoll
it , you must call close()
close, otherwise it may be fd
exhausted.
epoll_ctl
epoll
The 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 epoll
the events that have been sent in the monitored events. The parameter events
is an array of allocated epoll_event
structures, epoll
which will assign the events that occurred to events
the 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 ). maxevents
Tell the kernel events
how big this is, maxevents
the value of this cannot be greater than epoll_create()
when it was created size
, and the parameter timeout
is 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
socketpair
socketpair()
Function used to create a pair of unnamed, interconnected sockets.
Specific use reference
fd
linux
In 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
2.1.2 EventHub
2.1.2.1 INotify
INotify
It 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/input
be added to INotify
, so that the plugging and unplugging of external input devices can be well detected. Unfortunately, INotify
after discovering the file system changes, it will not take the initiative to tell others. It needs to actively read()
read inotifyFd
the descriptor through the function to obtain the specific change information. That is to say, when you insert the mouse, INotify
you immediately know the information, and update the information to inotifyFd
the object corresponding to the descriptor
2.1.2.2 Eventhub
Epoll
inotifyFd
It 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 Epoll
you can monitor inotifyFd
whether 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
epoll_wait
blocked state most of the time (this is very similar to the socket waiting for connection). Once the /dev/input/event0
node If there is a change (that is, an input event is generated), epoll_wait
it will be executed
2.1.3 IMS
2.1.3.1 InputReaderThread
InputReader
will be read continuously EventHub
in a loop原始输入事件
2.1.3.2 InputDispatcherThread
InputDispatcher
Save WMS
all the information in Window信息
( WMS
the information of the window will be updated in real time InputDispatcher
), so that InputDispatcher
the 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
InputManagerService.InputManagerService
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
2.1.3.3.3 Start the above two threads through IMS.start
SystemServer.startOtherService
InputManagerService.start
com_android_server_input_InputManagerService.cpp
Inputmanager.cpp
2.1.3.3.4 InputRerader thread processing process
overall indication
2.1.3.3.4.1 Thread running
InputReader.cpp
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
2.1.3.3.4.4 Finally call Device->process
processEventsForDeviceLocked
2.1.3.3.4.5 Call
InputDevice::process
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
2.1.3.3.4.7 TouchInputManager::sync
2.1.3.3.4.8 Then the final data flow
1.processRawTouches(false / timeout /);
2.cookAndDispatch
3.dispatchTouches
4.dispatchMotion
5. The actual acquisition of the current getListener is mQueuedListener = new QueuedInputListener(listener); example
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.
7. At this time, we are returning After the above processEventsLocked call, I found the current mQueueListener->flush call
8.InputLintener.cpp.QueuedInputListener::flush
To traverse the entire mArgsQueue
array, the implementation subclasses of NotifyArgs in the input architecture mainly include the following categories
NotifyConfigurationChangedArgs
NotifyKeyArgs
NotifyMotionArgs 通知motion事件
NotifySwitchArgs
NotifyDeviceResetArgs
9.
The listener here in NotifyMotionArgs can be seen to be InputDispatcher according to the traceability.
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
in notifyMotion
2.1.3.3.5 InputDispatcher thread processing process
Overall process schematic
2.1.3.3.5.1 InputDispatcher.cpp.threadLoop()
2.1.3.3.5.2 InputDispatcher.cpp.dispatchOnce()
pollOnce
Here is waiting to be woken up, enter epoll_wait
the waiting state, and exit the waiting state when any of the following situations occur:
callback
: wake up through the callback method;
timeout
: arrive at nextWakeupTime
the time, wake up after timeout;
wake
: actively call Looper
the wake()
method;
2.1.3.3.5.3 dispatchOnceInnerLocked
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
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
forceground window
window
window
handleTargetsNotReadyLocked
window
addWindowTargetLocked
inputChannel
InputTarget
2. dispatchEventLocked is responsible for the specific distribution target
1. dispatchEventLocked
2.prepareDispatchCycleLocked
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.
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:
5. So far, the publishMotionEvent method of the inputPublisher of the connection is called to distribute and consume the event.
InputTransport.cpp
2.2 Process from IMS to WMS
2.2.1 InputChannel
2.2.1.1 Concept
InputReaderThread
And the user process InputDispatcherThread
running in SystemServer
the process
is not in the same process as it, so there must be an inter-process communication mechanism in it. The
InputChannel
main core purpose is to communicate with the user process. This InputChannel
is WMS管理
triggered by build build
2.2.1.2 Model schematic
2.2.1.3 Create InputChannel process
1. The call that will be triggered in the function ViewRootImpl
in 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 communicationsetView
session.addToDispaly
WMS
addWindow
addTodisplay
InputChannel
addWindow
WindowState
openInputChannel
Linux implements a socketpair
call to implement the above functions of reading and writing in the same file descriptor (this call is also POSIX
part 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 socket
as the returned file descriptor, the only difference is that any one of the pair of pipe
file descriptors is readable and writable.
The channel is registered in the InputDispatcher , and
finally the InputDispatcher.cpp.registerInputChannel function is called.
At this point, the current InputChannel
object is passed in InputDispatcher
, and a certain encapsulation is carried out, and the window handle is passed in at the same time.
2.2.1.4 Enable the monitoring of input events to receive events
1. setView
In the middle, after we created it InputChannel
, we started to InputChannel
listen to the input event
in ViewRootImpl.setview
. Here is the structure called to the parent class.
2. Build a listener
android_view_InputEventReceiver.cpp in the native layer to listen to the event
. The operation performed by this method is Adding epoll
monitoring to the passed fd Looper
will call the method cyclically pollOnce
, and pollOnce
the 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 socket
monitoring the opening. When data arrives, we will execute the corresponding callback. The callback here InputChannel
is to call NativeInputEventReceiver的handleEvent
method
3. After the message is received, the callback
android_view_InputEventReceiver.cpp::handleEvent
android_view_InputEventReceiver.cpp::consumeEvents
consume
android_view_InputEventReceiver.cpp::consumeEvents
After the above message is received consume
, consumeEvent
it will return to the middle after execution. Judgment processing based on the read data, and then up call
to the Java function
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
2.ViewRootImpl.WindowInputEventReceiver.onInputEvent
3.ViewRootImpl.QueuedInputEvent.enqueueInputEvent
4.ViewRootImpl.QueuedInputEvent.doProcessInputEvents
5.ViewRootImpl.QueuedInputEvent.deliverInputEvent
6. In ViewRootImpl
, there are a series of similar InputStage(输入事件舞台)
concepts,
each of which InputStage
can handle a certain type of event,
and the subclasses are: AsyncInputStage
, ViewPreImeInputStage
, ViewPostImeInputStage
etc.
For the click event, ViewPostImeInputStage
it can be handled.
ViewPostImeInputStage
In, there is a processPointerEvent
method,
which will be mView
called dispatchPointerEvent
, Note that here mView
is actuallyDecorView
Note that the subclass must be called here. onProcess
Remember who is the View here? ? Here you can see the class that is DecorView!
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 ofView
dispatchPointerEvent
dispatchTouchEvent
DecView
window
callback
callback
Activity.attach()
activity
So what is called here Activity
isdispatchTouchEvent
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
- Reduce coupling . It decouples the sender and receiver of a request.
- Objects are simplified . So that the object does not need to know the structure of the chain.
- 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.
- 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:
dispatchTouchEvent
In the chain of responsibility, the event
onInterceptTouchEvent
is handed over to the next level for processing. In the chain of responsibility, the method of handling the transaction itself
onTouchEvent
is the event chain reported by the event in the chain of responsibility.
3.2 Which objects are passed between events
3.3 Event distribution sequence
3.4 What methods are used to coordinate the event distribution process?
3.5 Explanation on U-chain and L-chain in event distribution and consumption
3.5.1 Event distribution process
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
Activity
The thing to do is to forward it to the lower layer. In the case of no consumption, the last Activity
reason is wrap up the bottom line
3.5.1.2 ViewGroup
things to do
- Whether to intercept at the container level
- Whether the time should be distributed, and the cancellation event does not need to be consumed
- Before passing sub-
View
consumption, it depends on whether it is valid and which one is clickedView
on - 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:
- Check if the event is intercepted
- Check subviews and pass events
- Handle events yourself
Overall process:
DOWN
event, by polling the currentViewGroup
lowerView
- Synchronously judge whether it is
View
within the range of this rectangle - If it is, then select this
View
todispatch
forward, heViewGroup
will continue to forward the lower layer, if it isView
direct consumption processing! MOVE
Type, because of its characteristics, the amount is large, and it is not suitable to use polling- Therefore,
DOWN
when you come down, directly record the current oneView
, and then forward this directly by defaultView
- If the finger draws the current
View
, the event informationActionID
will be changed, after changing the ID,target
the change will be made!UP\ CANCEL
will 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;
}
listener
The one that is called first onTouch
, and onTouchEvent
the one that is called is based on onTouch
the result.
onTouch
The priority of the one that is used is higher than onTouchEvent
the onTouch
one 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:
- The event signal is the physical file storage data, location:
dev/input
linux
There are related file monitoring APIs, which useinotify(能监控文件变化产生FD)
andepoll(可以监控FD,以此配合完成文件的监控与监听)
Android
Wrote two threads to handledev/input
the following signals (InputReaderThread
andInputDispathcerThread
)EventHub
A special object is written , which is used to monitor the nextinotify+epoll
!dev/input
- Put this object into
InputReaderThread
it for execution, rotation traininggetEvent()
, whichepoll_wait
is equivalent towait-notify
the mechanism, the trigger point of the wake-up is that/dev/input
the file under it is changed, and the file is pushed by the driver to push the data InputReaderThread
/dev/input
The following data will be extracted, packaged, and then handed over toInputDispathcerThread
InputDispathcerThread
Give the final choice to the correspondingViewRootImpl(Window)
- The communication mechanism in the middle
socketpair
is carried out through a group of people on both sidessocketpair
- Then monitor the connected files in
ViewRootImpl
the middle , once the signal can be finally received by the upper layer!Channel
touch