Android 窗口机制 SDK31源码分析 总目录
- 初识
- DecorView与SubDecor的创建加载
- Window与Window Manager的创建加载
- ViewRootImpl的创建以及视图真正加载
- ViewRootImpl的事件分发
- 一定要在主线程才可以更新UI吗?为什么?
- Activity的Token和Dialog的关系
- Toast机制 - 封装可以在任何线程调用的toast
- 总结
上一章节,我们知道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 inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
...
//在上一章节,就讲的很清楚了,这里会通IPC调用到WMS,添加window。但是注意,此时把inputChannel也传递过去了,被WMS持有用作事件分发。
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
...
//通时inputChannel也被WindowInputEventReceiver所持有,接受WMS分发过来的事件。
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) {
...
//注意:最后一个参数传入了true
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;
...
//由于调用传进来的是true,这里执行doProcessInputEvents
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();
//通过deliver最终会调用到,InputStage具体实现类的onProcess方法。
stage.deliver(q);
} else {
finishInputEvent(q);
}
...
}
//这里说明一下,在ViewRootImpl的setView方法中,还会初始化,这么几个InputStage,用于处理不同的事件。
//对于点击事件来说,则最终会交由ViewPostImeInputStage进行处理
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;
...
//调用了dispatchPointerEvent
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();
}
//通过PhoneWindow调用到DecorView的superDispatchTouchEvent方法,DecorView调用了ViewGroup的dispatchTouchEvent,开启事件分发。
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果没有拦截处理,则自己消费处理
return onTouchEvent(ev);
}
说明一下:这里面的getWindow().superDispatchTouchEvent(ev)
,最终会调用到DecorView的superDispatchTouchEvent
方法,而DecorView的superDispatchTouchEvent
会调用ViewGroup的dispatchTouchEvent
,进而开启我们所熟知的事件分发。
看了上面的流程可能大家有点不理解,为什么要从DecorView绕道Activity,之后再给DecorView呢?
因为绕一圈,才可以将Activity作为事件分发的起点呀,当大家都不消费的事件的时候,事件就流回Activity了。
总结
直接看流程图就好
下一章节预告: 一定要在主线程才可以更新UI吗?为什么?
创作不易,如有帮助一键三连咯🙆♀️。欢迎技术探讨噢!