前言
android 中的事件分发,已经是老生常谈的问题了,一般都是从Activity开始聊,但是真的是这样吗?hongyang大神之前一篇纠错,解决了我一个很大的困惑,也算是扫盲吧,这篇文章,是从头到尾来梳理一遍,android事件分发。
楼主这两天发现一个博主比我写的好的多,特贴出地址如下:
反思|Android 事件分发机制的设计与实现
流程
我自信不会比网上的很多大神写的博客更好,所以这一篇相当于组合,把相关代码组合在一起。
这里我不会贴过多代码,但会给出相关参考连接。其实很多东西不需要一行一行去读代码,知道流程,场景,问题,应对方式就可以了,就能学以致用了。
首先,事件分发的起点在哪里?这里大家其实可以去网上搜索,其实很多博客都有介绍,是InputReader去读取硬件事件,然后分发到InputDispatcher,这部分博客流程和细节可以参考Android Input(三)-InputReader获取事件,还有Android触摸事件的传递(四-1)–输入系统-InputReader,当然也有偏向硬件层面的Android事件分发完全解析之事件从何而来,大家也可以自己去网上搜索,搜索InputReader就行。
然后经过InputDispatcher来进行分发,这里详细的流程可以参看gityuan大神的博客:Input系统—ANR原理分析,流程很详细。
其中涉及到三个队列,以及InputChannal,InputChannal是个很有意思的东西,之前看源码都没有在意过,后来才发现,原来他的初始化,就是在ViewRootImpl里面,详细可以参考博客Android输入系统IMS(2)–基础知识socketpair,InputChannal其实就是通过socket来进行通信,注意其中一句话
实际上socketpair 函数跟pipe 函数是类似的,也只能在同个主机上具有亲缘关系的进程间通信,但pipe 创建的匿名管道是半双工的,而socketpair 可以认为是创建一个全双工的管道。
摘抄自 Android输入系统IMS(2)–基础知识socketpair
与pipe的区别:pipe是单工通信,一端要么是读端要么是写端,而socketpair实现了双工套接字,也就没有所谓的读端和写端的区分。
摘抄自Android Framework 输入子系统(02)核心机制 双向通信(socketpair+binder)
InputChannal既然是通过ViewRootImpl里面的setView来创建的,但是Socket的创建并不在应用端,而是在WMS端,再把InputChannel和socket绑定起来,这里有篇博客关于InputChannel和SocketPair这块总结的很好,Android Input(五)-InputChannel通信
这里如果对SocketPair不了解或者有疑问的话,可能无法理解soketPair怎么明明在wms端创建,怎么实现和app端通信。其实重点就是socket的fd,以上的博客有讲解,可以了解下。
接下来,就进入了我们的事件分发流程体系了(View体系)。
上面这几篇博文几乎囊括了事件分发的framework层所有知识点,暂时不涉及到我们应用层的事件分发,因为这一块实在是烂大街了,随便一搜索到处都是,我就不提供参考博客了。从这里,我们也能看到其中一些,容易搞混的知识点,下面简单提出几个,以后遇到了,会不定期来更新。
问题
Activity是事件分发的起点吗?
这个问题,说是也不是,说不是也是。说是Activity,是因为我们上层能感知到的,确实是从Activity开始。但是,如果阅读了上面的内容,我相信你一定清楚,作为和InputDispatcher交互的通道,首先到达的肯定是ViewRootImpl,因为是通过InputChannel通信的。注意WMS和InputDispatcher是一个进程,不同线程。
那么先到达VRI(以后ViewRootImpl都简写为VRI)的哪里呢?
在setView里面,同时注册了一个WindowInputEventReceiver,并把InputChannel和Looper传进去了。其中有一个方法名叫onInputEvent,很明显,这里就是接收输入事件的。
再看onInputEvent的父类InputEventReceiver的怎么说:
//InputEventReceiver:
/**
* Called when an input event is received.
* The recipient should process the input event and then call {@link #finishInputEvent}
* to indicate whether the event was handled. No new input events will be received
* until {@link #finishInputEvent} is called.
*
* @param event The input event that was received.
*/
@UnsupportedAppUsage
public void onInputEvent(InputEvent event) {
finishInputEvent(event, false);
}
//这里是唯一调用onInputEvent的地方。
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
从Called from native code.就能看出来是底层的回调了。
看着这种方式,有没有很熟悉?没错,同样是ViewRootImpl,用来监听底层屏幕刷新回调的Choreographer也是使用这种方式。
具体注册是在
//DisplayEventReceiver
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
@UnsupportedAppUsage
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
底层回调是在
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
onVsync(timestampNanos, physicalDisplayId, frame);
}
好了,这个问题就说到这。
分发流程?
事件分发流程,老生长谈了,看完了前面的几篇博客,几乎也明白整个流程,我们这里再强调下,我们不说前面的,也不说后面的view分发,说说中间的,毫无疑问,最开始到达,InputEventReceiver之后,会通过Window.Callback来传递给Activity:
@Override
public boolean dispatchTrackballEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
}
这里的Window.Callback就是Activi