In the last article, we learned the drawing process of View from the perspective of the interviewer. In this article, we will talk about android event distribution.
First, topic level
DecorView -> ViewGroup -> View dispatchTouchEvent What knowledge points can be drawn out deeply from this?
- How does an event get from the screen click to the Activity?
- When is the CANCEL event triggered?
- How do I resolve sliding conflicts?
Second, detailed explanation of the topic
2.1 Distribution of Android events
Android event dispatch will probably go through Activity -> PhoneWindow -> DecorView -> ViewGroup -> View’s dispatchTouchEvent. DispatchTouchEvent can be explained with the following pseudocode, the process is not specific analysis, we should also be relatively clear.
/ / pseudo code
public boolean dispatchTouchEvent(a) {
boolean res = false;
// Whether intercepting events is not allowed
/ / if set FLAG_DISALLOW_INTERCEPT, not intercept events, so the child can intercept events by requestDisallowInterceptTouchEvent control whether the parent View
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept && onInterceptTouchEvent()) {// View does not call here, but executes the following touchListener judgment directly
if (touchlistener && touchlistener.onTouch()) {
return true;
}
res = onTouchEvent(); -> performClick() -> clickListener.onclick ()
} else if (DOWN) { // If the event is DOWN, the sub-view is iterated for event distribution
// Loop the child View to handle events
for(childs) { res = child.dispatchTouchEvent(); }}else {
// The event is sent to the target for processing. In this case, the target is the View that handled the DOWN event last step
target.child.dispatchTouchEvent();
}
return res;
}
Copy the code
2.2 How does an event arrive at an Activity
Since the event distribution above starts with the Activity, how does the event get to the Activity?
The general process looks like this: When the user clicks on the device, the Linux kernel accepts the interrupt, which is processed into input event data and written to the corresponding device node. InputReader monitors all device nodes under /dev/inpu/. When a node has data to read, EventHub takes out the original event and translates it into an input event, which is delivered to InputDispatcher. InputDispatcher delivers the event to the appropriate window according to the window information provided by WMS, and the window ViewRootImpl dispatches the event
The general flow chart is as follows:
There are mainly several stages:
- Hardware interrupt
- InputManagerService Does something
- InputReaderThread
- InputDispatcherThread does something
- WindowInputEventReceiver
2.2.1 Hardware Interruption
Hardware interrupts are briefly introduced here. The operating system receives hardware events through interrupts. The kernel registers the address of the interrupt type and the corresponding handling method in the interrupt descriptor table at startup. When there is an interrupt, the corresponding handler is called to write the corresponding event to the device node.
2.2.2 What InputManagerService does
InputManagerService is used to handle Input events. The Java side of InputManagerService is a wrapper around the C++ code and provides some callback to pass events to the Java layer. Let’s take a look at the InputManagerService initialization code on the Native side.
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
// ...
sp<EventHub> eventHub = new EventHub(a); mInputManager =new InputManager(eventHub, this.this);
}
Copy the code
There are two main things to do:
- Initialize EventHub
EventHub::EventHub(void) {
// ...
mINotifyFd = inotify_init(a);int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}
Copy the code
The EventHub function is to monitor whether device nodes are updated. 2. Initialize InputManager
void InputManager::initialize(a) {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
Copy the code
InputManager initializes two threads, InputReaderThread and InputDispatcherThread. One is used to read events and the other is used to dispatch events.
2.2.3 InputReaderThread
bool InputReaderThread::threadLoop(a) {
mReader->loopOnce(a);return true;
}
void InputReader::loopOnce(a) {
// Get events from EventHub
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
// Handle events
processEventsLocked(mEventBuffer, count);
// Events are sent to InputDispatcher for distribution
mQueuedListener->flush(a); }Copy the code
There’s a lot of code here, so I’m going to omit it. InputReaderThread does three things:
- Get the event from EventHub
- Handle events, where there are different types of events that are handled and encapsulated differently
- Send events to InputDispatcher
2.2.4 InputDispatcherThread Does
bool InputDispatcherThread::threadLoop(a) {
mDispatcher->dispatchOnce(a);// Call dispatchOnceInnerLocked internally
return true;
}
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// Retrieve an event from the queue
mPendingEvent = mInboundQueue.dequeueAtHead(a);// Perform different operations according to different event types
switch (mPendingEvent->type) {
case EventEntry::TYPE_CONFIGURATION_CHANGED: {
// ...
case EventEntry::TYPE_DEVICE_RESET: {
// ...
case EventEntry::TYPE_KEY: {
// ...
case EventEntry::TYPE_MOTION: {
// Send events
done = dispatchMotionLocked(currentTime, typedEntry,
&dropReason, nextWakeupTime);
break; }}Copy the code
Events are dispatched via dispatchMotionLocked. The function calls are omitted as follows:
dispatchMotionLocked -> dispatchEventLocked -> prepareDispatchCycleLocked -> enqueueDispatchEntriesLocked -> startDispatchCycleLocked -> publishMotionEvent -> InputChannel.sendMessage
Copy the code
It finds the currently appropriate Window and calls InputChannel to send the event.
The InputChannel here corresponds to the InputChannel in view owl pl. As for the middle of how to do the correlation, here will not do the analysis, the whole code is relatively long, and for the grasp of the process has little impact.
2.2.5 WindowInputEventReceiver Receives events and distributes them
In view Windows PL there is a WindowInputEventReceiver for receiving events and distributing them. Events sent by InputChannel are finally accepted by the WindowInputEventReceiver. The WindowInputEventReceiver is initialized in viewrootimpl. setView. Call setView is the ActivityThread. HandleResumeActivity – > WindowManagerGlobal. AddView.
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// ...
if(mInputChannel ! =null) {
if(mInputQueueCallback ! =null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = newWindowInputEventReceiver(mInputChannel, Looper.myLooper()); }}Copy the code
public abstract class InputEventReceiver {
// The native side code calls this method and dispatches the event
private void dispatchInputEvent(int seq, InputEvent event, int displayId) { mSeqMap.put(event.getSequenceNumber(), seq); onInputEvent(event, displayId); }}final class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event, int displayId) {
// Event accept
enqueueInputEvent(event, this.0.true);
}
// ...
}
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
// Whether to process the event immediately
if (processImmediately) {
doProcessInputEvents();
} else{ scheduleProcessInputEvents(); }}void doProcessInputEvents(a) {
// ...
while(mPendingInputEventHead ! =null) {
deliverInputEvent(q);
}
// ...
}
private void deliverInputEvent(QueuedInputEvent q) {
// ...
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
// Distribute events
stage.deliver(q);
}
Copy the code
From the code flow above, events eventually go to inputStage.Deliver.
abstract class InputStage {
public final void deliver(QueuedInputEvent q) {
if((q.mFlags & QueuedInputEvent.FLAG_FINISHED) ! =0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else{ apply(q, onProcess(q)); }}}Copy the code
In Deliver, the onProcess is finally called, and the implementation is in the ViewPostImeInputStage.
final class ViewPostImeInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if((source & InputDevice.SOURCE_CLASS_POINTER) ! =0) {
return processPointerEvent(q);
} else if((source & InputDevice.SOURCE_CLASS_TRACKBALL) ! =0) {
return processTrackballEvent(q);
} else {
returnprocessGenericMotionEvent(q); }}}private int processPointerEvent(QueuedInputEvent q) {
/ / here is mView DecorView, call to DecorView. DispatchPointerEvent
boolean handled = mView.dispatchPointerEvent(event);
// ...
returnhandled ? FINISH_HANDLED : FORWARD; }}// View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
returndispatchGenericMotionEvent(event); }}// DecorView.java
public boolean dispatchTouchEvent(MotionEvent ev) {
// Call mwindow.setcallback (this) in activity. attach; Set up the
final Window.Callback cb = mWindow.getCallback();
returncb ! =null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
Copy the code
Through the above a series of processes, finally is invoked to the Activity. The dispatchTouchEvent, also is the start of the process.
From the above analysis, we basically know the process of the event from the user clicking the screen to the View, which is shown below.
2.3 When will the CANCEL event be triggered
If you look closely at the dispatchTouchEvent code, you can see some moments:
- After the View receives an ACTION_DOWN event, the previous event has not ended (the system may discard subsequent events due to APP switchover or ANR). In this case, the View will perform an ACTION_CANCEL first
// ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 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(); }}Copy the code
- The child View intercepts the event, but the parent View intercepts the event again, which sends the ACTION_CANCEL event to the child View
// ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mFirstTouchTarget == null) {}else {
// A child View got the event
TouchTarget target = mFirstTouchTarget;
while(target ! =null) {
final TouchTarget next = target.next;
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
CancelChild is true if the parent View intercepts the event
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true; }}}}private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final int oldAction = event.getAction();
// If cancel is true, an ACTION_CANCEL event is sent
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
returnhandled; }}Copy the code
2.4 How to Resolve sliding conflicts
This is also a platitude of a problem, there are two main methods:
- Intercepting sliding events by overriding onInterceptTouchEvent of the parent class
- By calls to the parent in subclasses. RequestDisallowInterceptTouchEvent whether to notify the parent to intercept events, RequestDisallowInterceptTouchEvent FLAG_DISALLOW_INTERCEPT flag, this done at the beginning of pseudo code there is introduced
Third, summary
The above is from the View event distribution derived from some questions, simple answers are as follows:
- View Event Distribution
/ / pseudo code
public boolean dispatchTouchEvent(a) {
boolean res = false;
// Whether intercepting events is not allowed
/ / if set FLAG_DISALLOW_INTERCEPT, not intercept events, so the child can intercept events by requestDisallowInterceptTouchEvent control whether the parent View
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept && onInterceptTouchEvent()) {// View does not call here, but executes the following touchListener judgment directly
if (touchlistener && touchlistener.onTouch()) {
return true;
}
res = onTouchEvent(); -> performClick() -> clickListener.onclick ()
} else if (DOWN) { // If the event is DOWN, the sub-view is iterated for event distribution
// Loop the child View to handle events
for(childs) { res = child.dispatchTouchEvent(); }}else {
// The event is sent to the target for processing. In this case, the target is the View that handled the DOWN event last step
target.child.dispatchTouchEvent();
}
return res;
}
Copy the code
- How does an event get from the screen click to the Activity?
- When is the CANCEL event triggered?
- After the View receives an ACTION_DOWN event, the previous event has not ended (the system may discard subsequent events due to APP switchover or ANR). In this case, the View will perform an ACTION_CANCEL first
- The child View intercepts the event, but the parent View intercepts the event again, which sends the ACTION_CANCEL event to the child View
- How do I resolve sliding conflicts?
- Intercepting sliding events by overriding onInterceptTouchEvent of the parent class
- By calls to the parent in subclasses. RequestDisallowInterceptTouchEvent whether to notify the parent to intercept events
The article is constantly updated. Search “ZYLAB” on wechat and get the update immediately. Reply to “Simulated interview” to unlock the one-to-one interview experience of Big factory