touch 事件(一)

2,158 阅读8分钟

总流程

  1. Touch 事件由 ims 通过 epoll 机制读取 /dev/input/eventx 产生,经 Socket 交由应用进程
  2. 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 的创建后面再说。

总结一下,里面涉及到多少集合:

  1. InputReader 负责从 eventHub 中读事件,将读到的放到 InboundQueue 中,简称 iq
  2. InputDispatcher 负责从 InboundQueue 中读,将读到的放到 Connection 的 OutboundQueue 中,简称 oq
  3. Connection 负责从 OutboundQueue 中读,将读到的事件发给应用进程,同时放到 waitQueue 中,简称 wq
  4. 应用进程收到后会将事件放到 QueuedInputEvent 中(简称 aq),然后 java 层经过一系列的 stage 到 View 处理
  5. 应用进程处理完后回调 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,随后就是正常的事件分发流程了。

总结

  1. ims 通过 epoll 监听 /dev/input 目录下的文件,得到事件发生时的通知。注意,此时的事件包括设备的加载、卸载,按钮输入等,并不只是 touch 事件
  2. 经预处理后交给 wms,wms 通过 socket 将事件发送给应用进程
    • 应用进程与 wms 所在进程通过一对相交连接的 Socket 进行通信。也就是说 wms 在一个 socket 写,应用进程通过另一个 socket 就可以读到
  3. 应用进程会将 socket 对应的 fd 添加到主线程的 Looper 中。准确地说,将 fd 添加到 Looper 对应的 epoll 观测列表中
  4. 当 wms 通过 socket 发事件时,应用进程的 epoll 就会被唤醒,然后开始对事件进行处理。这就是为什么 handler 在没有 message 要进行处理时,会陷入阻塞但不会 anr 的原因,因为事件到来时,会唤醒主线程