总流程
- Touch 事件由 ims 通过 epoll 机制读取 /dev/input/eventx 产生,经
Socket交由应用进程 - Java 层接收 Touch 事件的第一个方法是
InputEventReceiver#dispatchInputEvent,它的实例在 ViewRootImpl 中创建。也就是说touch 事件并不经过 Handler#dispatchMessage,这就是通过设置 printer 监听卡顿会漏报 touch 事件的原因
IMS 简要说明
本篇并不会详细解析 ims 源码,只简要说明。
在说明会涉及到不同部分时会列出一些参考博客,其中关于 InputReader 部分现在的版本(以 cs.android 中搜到的内容为准)与博客有所不同,但整体逻辑不变,没必要纠结
IMS 与 WMS 交互
ims 与 wms 的关系在 SystemServer 创建时就已注定:
// SystemServer#startOtherServices() 节选:
inputManager = new InputManagerService(context);
// wms 直接持有 Ims 引用,使用 mInputManager 指向 ims
wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
// 设置回调,是 InputManagerCallback 类型的实例
// 回调通过 mService 指向 wms
inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
inputManager.start();
native 层
ims 的构造函数中会调用 nativeInit(),其 start 方法中会调用 nativeStart(),这样便由 java 层启动了 native 层。
ims 在 native 层主要有 InputReader 与 InputDispatcher 两个线程:前者负责采集事件,后者负责分发。其中 InputReader 内部是通过 EventHub 采集数据,它只是负责将事件分发给到 InputDispatcher::mInboundQueue 中
采集部分可参考 www.jianshu.com/p/7c7b9332d… 。简单说:InputReader 通过 EventHub 采集数据,EventHub 又通过 epoll 从 /dev/input 中读取各种事件,将事件经封装后添加到 InputDispatcher::mInboundQueue 中,随后会唤醒 InputDispatcher 线程
事件分发部分可参考 Android 触摸事件分发机制(一)从内核到应用 一切的开始 中关于 InputDispatcher 部分。
简单说,分发从 InputDispatcher::disptachOnce() 开始,对于 touch 事件它最终会调用到 dispatchMotionLocked(),该方法内部首先通过 findTouchedWindowTargetsLocked() 找到能处理 touch 事件的 window,然后调用 dispatchEventLocked(),后者调用 prepareDispatchCycleLocked()。
到 prepareDispatchCycleLocked() 后,具体源码分析可看 gityuan 关于 input 的分析,这一部分在新旧版本上基本没有变化。这里还是简单说,prepareDispatchCycleLocked() 会调用 enqueueDispatchEntriesLocked(),它会往 Connection::outboundQueue 中添加元素,然后再调用 startDispatchCycleLocked(),后者会遍历 outboundQueue,根据事件类型调用不同的方法。对于 touch 事件,它调用的是 connection->inputPublisher.publishMotionEvent,调用完后会将元素添加到 connection->waitQueue 中,这里放一点代码:
// startDispatchCycleLocked() 节选
// 将 dispatchEntry 从 outboundQueue 中删除
// 到此步时,已调用过 connection->inputPublisher.publishMotionEvent
connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),
connection->outboundQueue.end(),
dispatchEntry));
connection->waitQueue.push_back(dispatchEntry); // 添加到 waitQueue 中
到 publishMontionEvent() 中,就是简单地调用 InputChannel::sendMessage(),通过 socket 将事件发送给应用进程。
关于 InputChannel 以及 socket 的创建后面再说。
总结一下,里面涉及到多少集合:
- InputReader 负责从 eventHub 中读事件,将读到的放到
InboundQueue中,简称 iq - InputDispatcher 负责从 InboundQueue 中读,将读到的放到 Connection 的
OutboundQueue中,简称 oq - Connection 负责从 OutboundQueue 中读,将读到的事件
发给应用进程,同时放到waitQueue中,简称 wq - 应用进程收到后会将事件放到
QueuedInputEvent中(简称 aq),然后 java 层经过一系列的 stage 到 View 处理 - 应用进程处理完后回调 ims, 将事件从 waitQueue 中移除
上面就是大概的流程,
InputChannel 的建立
InputChannel 其实只是对 SOCKET 的一套封装。它的建立需从 ViewRootImpl#setView() 开始说起:
mInputChannel = new InputChannel();
// 将 mInputChannel 传入 wms 中,wms 填充 mInputChannel
// 这一步后面再看,它的真正实现是 WMS#addWindow()
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
// 设置一个接收者
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
上面在 addToDisplay() 时会将 InputChannel 传给 wms,在 IWindowSession.aidl 中 InputChannel 被 out 修饰,根据 aidl 相关知识可知:wms 会为传入的 mInputChannel 填充内容。现在假设 wms 已处理完成,先看客户端的流程。
客户端处理
客户端拿到 mInputChannel 后,会创建一个 WindowInputEventReceiver 对象,它继承 InputEventReceiver,后者在构造函数中调用 nativeInit():
// InputEventReceiver.java
// 构造函数
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
// nativeInit 就到 native 层,native 层会创建 NativeInputEventReceiver 对象,然后调用其 initialize() 方法,initialize() 又调用 setFdEvents(int events)
// NativeInputEventReceiver 构造函数节选(c++)
// receiverWeak 就是 java 层的 InputEventReceiver 对象
// 也就是 ViewRootImpl 中创建的 WindowInputEventReceiver 对象
mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
mInputConsumer(inputChannel),
mMessageQueue(messageQueue)
// setFdEvents()
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
// -> 前一部分就是外面传入的 inputChannel
// getFd() 返回的是跟 inputChannel 绑定的 Socket 对应的 fd
// 这一部分后面说明
int fd = mInputConsumer.getChannel()->getFd();
// 从 initialize() 来时,if 判断成立
if (events) {
// 将 fd 添加到 Looper 中,Looper 内部使用 epoll 机制监听该 fd
mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
} else {
mMessageQueue->getLooper()->removeFd(fd);
}
}
}
上面涉及到 native 层的 messageQueue 与 looper 的逻辑,不再展开说明,只需知道:当 fd 中有数据到来时会执行到 NativeInputEventReceiver#handleEvent() 即可,后者调用 consumeEvents(),它内部有如下代码:
receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
// 也就是说,最终到 java 层的 InputEventReceiver#dispatchInputEvent
// 然后由 java 层代码进行处理,里面就是 touch 事件的处理了
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
上面就是应用进程怎么从 ims 中收到 touch 事件,然后再回调到 java 层的逻辑。目前为止,缺的就是 fd 是如何来的,这一部分在 wms 中
wms 的处理
回到本大节中的第一段代码,里面调用了 windowSession#addToDisplay(),它最终执行 wms#addWindow
// wms#addWindow
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
final boolean openInputChannels = (outInputChannel != null
&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
win.openInputChannel(outInputChannel);
}
简单说,它会调用 WindowState#openInputChannel()
// openInputChannel() 节选
// openInputChannelPair() 会到 native 层,由 native 层构建两个 InputChannel 并返回
// 两个 InputChannel 各自含有一个 socket,这两个 socket 是由 socketpair 创建的,一端发数据另一端就可以收到
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
// 这一句很重要,留着 wms 到 ims 时再分析
mWmService.mInputManager.registerInputChannel(mInputChannel);
mInputWindowHandle.token = mClient.asBinder();
// outInputChannel 就是应用层传进来的 InputChannel
if (outInputChannel != null) {
// 转移到 outInputChannel,也即应用进程传过来的 InputChannel
mClientChannel.transferTo(outInputChannel);
mClientChannel.dispose();
mClientChannel = null;
} else {
}
关键是调用 InputChannel#openInputChannelPair(),它会执行到 native 层,native 层返回后会给应用进程传过来的 InputChannel 填充数据。下面看 native 层逻辑
// InputChannel#openInputChannelPair 对应的 native 方法
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
jclass clazz, jstring nameObj) {
// 生成 native 层的 InputChannel
sp<InputChannel> serverChannel;
sp<InputChannel> clientChannel;
// 这里会创建两个相互连接的 socket,使用它们分别构建 InputChannel,并赋值给 serverChannel 与 clientChannel
status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
// 创建一个两个元素的数组,用于返回
jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, nullptr);
// 下面依次为数组填充元素
// 分析见最下面,主要是利用创建好的 inputChannel 创建 java 层的 InputChannel
jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, serverChannel);
env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, clientChannel);
env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
return channelPair;
}
// 注意最后两个参数传入的是 sp<InputChannel>& 类型,也就是说方法里修改之后外面可以拿到最新值
status_t InputChannel::openInputChannelPair(const std::string& name,
sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
int sockets[2];
// 创建一对无名的、相互连接的 Socket,将它们的 fd 保存到数组中
// https://blog.csdn.net/xifens/article/details/53714814
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
// 判断成立,说明失败了,不看
return result;
}
// 对 socket 进行一些设置
sp<IBinder> token = new BBinder();
// 创建两个 InputChannel,使用的是上面创建的 socket
std::string serverChannelName = name + " (server)";
android::base::unique_fd serverFd(sockets[0]);
// 生成 InputChannel 对象,同时将第二个参数记录到自己的 mFd 属性中
outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);
std::string clientChannelName = name + " (client)";
android::base::unique_fd clientFd(sockets[1]);
outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);
return OK;
}
上面涉及到 android_view_InputChannel_createInputChannel(),它主要用来创建 java 层 InputChannel:
static jobject android_view_InputChannel_createInputChannel(JNIEnv* env,
sp<InputChannel> inputChannel) {
std::unique_ptr<NativeInputChannel> nativeInputChannel =
std::make_unique<NativeInputChannel>(inputChannel);
// 通过反射创建 java 层 InputChannel 对象
jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
gInputChannelClassInfo.ctor);
if (inputChannelObj) {
// 如果创建成功,就将 native 层的 InputChannel 地址设置给 java 层 InputChannel 中的 mPtr
// 这样 java 层通过 mPtr 就可使用 native 层
android_view_InputChannel_setNativeInputChannel(env, inputChannelObj,
nativeInputChannel.release());
}
return inputChannelObj;
}
至此,java 与 native 层的 InputChannel 都创建完毕,且 java 层通过 mPtr 指向 native 层,native 层通过 fd 引用 socket 对应的文件描述符。
而 socket 是通过 socketpair 创建的一对套接在一起的 socket,这对 socket 最终一端在 wms 中,一端会返回给应用进程。
wms 到 ims
现在回到最初的 ims 部分,ims 通过 InputDispatcher,再经过 socket 完成对事件的分发。但 socket 目前只要 wms 中,中间尚缺 wms 到 ims 的过程。这一部分的起始在 WindowState#openInputChannel(),在该方法中调用了
// WindowState#openInputChannel
mInputChannel = inputChannels[0];
// mWmService 就是 wms 实例,mWmService.mInputManager 就是 ims 实例
mWmService.mInputManager.registerInputChannel(mInputChannel);
上面调用到 ims,参数是 wms 自己端的 inputChannel,它可以与应用进程通信。 而 registerInputChannel 会调用 nativeRegisterInputChannel,进入到 native 层。剩余分析可参考 gityuan 关于 input 分析中 nativeRegisterInputChannel 部分,简单说就是最终会注册到 InputDispatcher 对应的 Looper 中
其他小知识
Instrumentation#sendPointerSync
用于在应用进程模拟点击事件。其实现逻辑非常简单,Ins 首先调用 wms#injectInputAfterTransactionsApplied(),wms 又调用 ims#injectInputEvent(),该方法最终执行到 native 层,在 native 层会向 InputDispatcher::mInboundQueue 中添加自定义的 touch 事件,然后唤醒 InputDispatcher,随后就是正常的事件分发流程了。
总结
- ims 通过 epoll 监听 /dev/input 目录下的文件,得到事件发生时的通知。注意,此时的事件包括设备的加载、卸载,按钮输入等,并不只是 touch 事件
- 经预处理后交给 wms,wms 通过 socket 将事件发送给应用进程
- 应用进程与 wms 所在进程通过一对相交连接的 Socket 进行通信。也就是说 wms 在一个 socket 写,应用进程通过另一个 socket 就可以读到
- 应用进程会将 socket 对应的 fd 添加到主线程的 Looper 中。准确地说,将 fd 添加到 Looper 对应的 epoll 观测列表中
- 当 wms 通过 socket 发事件时,应用进程的 epoll 就会被唤醒,然后开始对事件进行处理。这就是为什么 handler 在没有 message 要进行处理时,会陷入阻塞但不会 anr 的原因,因为事件到来时,会唤醒主线程