touch 事件(一)

总流程

  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 两个线程:前者负责采集事件,后者负责分发。

采集部分可参考 www.jianshu.com/p/7c7b9332d… 。简单说:InputReader 通过 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 的创建后面再说。

WMS 到应用进程

wms 收到事件后通过 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());
}
复制代码

先看上面创建 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);
        }
    }
}
复制代码

当 fd 有事件当来时,会唤醒 Looper,然后执行 NativeInputEventReceiver#handleEvent(),后者调用 consumeEvents(),它内部有如下代码:

receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
// 也就是说,最终到 java 层的 InputEventReceiver#dispatchInputEvent
// 然后由 java 层代码进行处理,里面就是 touch 事件的处理了
env->CallVoidMethod(receiverObj.get(),
        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
复制代码

上面就是应用进程怎么从 ims 中收到 touch 事件,然后再回调到 java 层的逻辑。现在缺的就是这一对 socket 是在哪创建的,主要看 WMS#addWindow() 中关于 InputChannel 的处理,它会调用 WindowState#openInputChannel

// openInputChannelPair() 会到 native 层,由 native 层构建两个 InputChannel 并返回
// 两个 InputChannel 各自含有一个 socket,这两个 socket 是由 socketpair 创建的,一端发数据另一端就可以收到
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
mInputWindowHandle.token = mClient.asBinder();
// outInputChannel 就是应用层传进来的 InputChannel
if (outInputChannel != null) {
    // 转移到 outInputChannel
    mClientChannel.transferTo(outInputChannel);
    mClientChannel.dispose();
    mClientChannel = null;
} else {
    
}

// 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;
}

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 层的 InputChannel 都有了对应的 native 层的 InputChannel,而且 native 层的 InputChannel 可通过 Socket 相互通信。

NativeInputChannel 一个位于用户进程,一个位于 wms,通过 socket 就可完成通信。

native 层总结

  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 的原因,因为事件到来时,会唤醒主线程

Guess you like

Origin juejin.im/post/7031871778519515167