Android 窗口机制 SDK31源码分析 总目录
上一章节,我们知道ViewRootImpl的绘制流程。
那么今天就来看一看ViewRootImpl的另一个大的功能点事件分发吧。
事件是如何被接收的呢?
首先需要思考一个问题,事件是如何传递到ViewGroup呢?
我们都知道Android底层是Linux内核,所以事件的传递过程大概如下所示:
在Android系统开机的过程中,会启动许多系统服务,其中包括InputManagerService 用来监听事件的输入,而WindowManagerService 作为事件输入中转站,管理输入事件,配合InputManagerService 将输入事件交由合适的Window ,即最终传输到ViewRootImpl 的InputChannel ,最终被ViewRootImpl 的WindowInputEventReceiver 所接受。
对于上述的流程,这里先简单看一下源码:
ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
...
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
...
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
...
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
...
}
上一章节我们已经很清楚了,setView 会在ActivityThread 调用handleResumeActivity 时被WindowManager 调用addView 所调用,用于添加DecorView,以及调用绘制流程。但是这里面也会初始化InputChannel 以及WindowInputEventReceiver 用于接收事件输入。
ViewRootImpl事件分发源码分析
好了接下来进入正题,看ViewRootImpl接收到事件之后是如何处理的。
ViewRootImpl
final class WindowInputEventReceiver extends InputEventReceiver {
...
public void onInputEvent(InputEvent event) {
...
enqueueInputEvent(event, this, 0, true);
}
...
}
我们直接看WindowInputEventReceiver 类,他是ViewRootImpl的内部类。其中onInputEvent 用于接收事件输入,它调用了enqueueInputEvent ,且最后一个参数传入了true 。
ViewRootImpl
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
...
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
...
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
在enqueueInputEvent 中,选获取了队列输入事件,之后将它加入链表末尾,然后调用了doProcessInputEvents 。
ViewRootImpl
void doProcessInputEvents() {
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
mPendingInputEventCount -= 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent));
deliverInputEvent(q);
}
...
}
这里循环取出事件,然后交予deliverInputEvent 进行处理。
private void deliverInputEvent(QueuedInputEvent q) {
...
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
...
if (stage != null) {
handleWindowFocusChanged();
stage.deliver(q);
} else {
finishInputEvent(q);
}
...
}
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
这个方法里面出现了InputStage ,它是一个基类,有各种不同的实现用来处理不同的事件,比如点击事件,我们可以看ViewPostImeInputStage 。
ViewPostImeInputStage
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 {
return processGenericMotionEvent(q);
}
}
}
这里会判断事件类型,比如触摸还是键盘输入?我们看触摸事件,此时会调用processPointerEvent 。
ViewPostImeInputStage
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
...
boolean handled = mView.dispatchPointerEvent(event);
...
return handled ? FINISH_HANDLED : FORWARD;
}
我们看到这里面调用了dispatchPointerEvent ,那么这个mView 是谁呢?当然是DecorView了,在setView 的时候传进来的嘛。
接下来看DecorView的dispatchPointerEvent ,但是找了一下竟然没发现!!!
好吧,在其父类View中发现了,我们看一眼
View
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
这里面依旧判断了是否是触摸,所以我们直接看dispatchTouchEvent ,由于我们知道mView 就是DecorView,而DecorView会实现dispatchTouchEvent 方法,所以我们需要看DecorView的dispatchTouchEvent 方法。
DecorView
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
而在前面的章节中我们知道,在ActivityThread调用HandleLaunchActivity 流程中,会调用到Activity的attach 方法,在这里会创建真正的PhoneWindow,同时执行了这样依据代码mWindow.setCallback(this); ,所以显而易见,Activity实现了Window.Callback 接口,并且被mWindow 所持有。所以这里面就调用到了Activity的dispatchTouchEvent 。
Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
说明一下:这里面的getWindow().superDispatchTouchEvent(ev) ,最终会调用到DecorView的superDispatchTouchEvent 方法,而DecorView的superDispatchTouchEvent 会调用ViewGroup的dispatchTouchEvent ,进而开启我们所熟知的事件分发。
看了上面的流程可能大家有点不理解,为什么要从DecorView绕道Activity,之后再给DecorView呢?
因为绕一圈,才可以将Activity作为事件分发的起点呀,当大家都不消费的事件的时候,事件就流回Activity了。
总结
直接看流程图就好
下一章节预告: 一定要在主线程才可以更新UI吗?为什么?
创作不易,如有帮助一键三连咯🙆?♀?。欢迎技术探讨噢!
|