让你知道InputChannel

  • 最近在研究Framework层的几大Service,感觉对InputManagerService理解起来相对没有这么费劲。尤其是看了WindowManagerService对窗口的layout计算之后,其实真的想吐槽代码写的并不是特别的好。起码可读性上来说吧,确实不太行。
  • 不过想想AMS和WMS这些代码从开始迭代到现在,毕竟Android一开始也是收购来的,估计想要重构也不是这么容易的事情。没办法,那就慢慢啃吧。
  • 今天先从比较感兴趣的InputChannel入手,聊一聊Android7以后的实现。

InputChannel是什么:

  • InputChannel从简单理解上来说,可以是Socket的封装实现。当然,这么说是比较不负责任的定义,但是大家可以暂且理解成InputChannel的底层就是用Socket实现的。大家都知道,Socket其实是一种通信协议,一方写入流,一方读取流,反过来也可以。那么应用到InputChannel上是什么意思呢?
  • 首先,Android的点击事件,是底层驱动接收,一层一层封装传递给上层一直到App这边来的,那么肯定会有通信的过程。我们可以简单理解成,底层是Socket的一方,我们称之为客户端吧。
  • 其次,接收时间的最终端,应该是App,那么我们就将App成为服务端吧。
  • 当然我们这么称呼只是为了方便,你也可以吧底层称为服务端,我这么做只是为了好区分两者而已。
  • 在InputChannel的两端,分别是客户端和服务端,当客户端(底层)接收到来自用户的Touch的时候,就将事件进行封装。如果是Touch事件,最终封装结果就是MotionEvent;如果是键盘事件,最终就是KeyEvent。封装的结果,通过InputChannel,传递给服务端(App),App的ViewRootImpl接收到来自InputChannel的事件,再做进一步的分发。
  • 可以简单理解为,在App开发过程中,我们的事件分发的前一步,就是InputChannel的事件源通信。如果没有InputChannel做传递,事件过不来,也就不存在分发的概念了。

底层原理:

  • 由于博主主要是Java层的小菜鸟,所以对底层的实现不是非常融会贯通,因此只能在这里给大家讲个大概。首先底层存在这样两个C++类:InputReader.cpp、InputDispatcher.cpp。
  • InputReader负责从驱动中的设备节点读取封装事件,将其交给InputDispatcher决定交给上层的哪一个窗口接收。而连接InputDispatcher和窗口的存在,就是我们刚才说到的InputChannel
  • 当然事件的传递肯定是通过InputDispatcher一层层的通过JNI最终回调到Java层来,这里就不贴代码了。各位有兴趣的同学可以自己研究下《深入理解Android卷III》,张大伟大神的版本。他对整个Android输入系统,剖析的十分透彻。

创建InputChannel:

  • 以下,我们回到了Framework层。此时,系统端我们默认称为:服务端;App端我们默认称为:客户端。

添加窗口:

  • 我们首先要知道,ViewRootImpl创建完毕之后,会调用服务端的接口addToDisplay()将自己的窗口通知WMS,添加到Display进行显示。此时,在addToDisplay的接口上,ViewRootImpl传入了一个自己创建的InputChannel对象。这个就是客户端自己保有的InputChannel对象,是事件传递的一端。
        if ((mWindowAttributes.inputFeatures
             & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            mInputChannel = new InputChannel();//创建InputChannel对象
        }
        ...
        try {
            ...
            res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                              getHostVisibility(), mDisplay.getDisplayId(),
                                              mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                              mAttachInfo.mOutsets, mInputChannel);//通知服务端添加窗口显示,并且传入InputChannel对象
        } catch (RemoteException e) {
            ...
        }

创建对象,建立连接:

  • 随后在WMS中的addWindow()接口将会被调用,此时,在这个借口里,WMS会调用底层,创建InputChannel并且和客户端的对象绑定起来,让InputChannel能够正常进行工作:
        final boolean openInputChannels = (outInputChannel != null
                                           && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);//调用WindowState对象,传入客户端的InputChannel,建立InputChannel通信系统
        }
  • 上述代码中的WindowState,是服务端对每一个窗口的表示,即每一个Window,都对应唯一一个WindowState对象。我们来看下实现:
        void openInputChannel(InputChannel outInputChannel) {
            if (mInputChannel != null) {
                throw new IllegalStateException("Window already has an input channel.");
            }
            String name = getName();
            InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//调用Native层,创建InputChannel通信系统。将会返回两个对象,一个指代客户端,一个指代服务端,用于两者通信。
            mInputChannel = inputChannels[0];//存放在WindowState中,留存于服务端的InputChannel对象
            mClientChannel = inputChannels[1];//存放在客户端中的InputChannel对象
            mInputWindowHandle.inputChannel = inputChannels[0];
            if (outInputChannel != null) {
                mClientChannel.transferTo(outInputChannel);//将Inputchannel对象传给客户端保存
                mClientChannel.dispose();
                mClientChannel = null;
            } else {
                // If the window died visible, we setup a dummy input channel, so that taps
                // can still detected by input monitor channel, and we can relaunch the app.
                // Create dummy event receiver that simply reports all events as handled.
                mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
            }
            mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);//将服务端的InputChannel对象注册到IMS中,毕竟就客户端来说,可能只有一个InputChannel对象。但是服务端需要管理的是整个系统千万个窗口,每个窗口都有一个InputChannel对象,也是蛮多的。
        }
  • 到这里,InputChannel就初始化完毕了,这时候客户端和服务端都分别保存了InputChannel对象,作为InputChannel这个管道的两端,双方之间就可以互通有无,狼狈为奸了(逃!!!)
  • 肯定有小伙伴觉得,为什么双方都要有InputChannel对象保留呢?明明底层将事件传递上来给App,只需要系统服务端这边单方面通知App客户端不就好了?
  • 事实上不是这样的。当客户端接收了服务端的事件以后,服务端怎么知道客户端有没有处理,有没有接收到呢?因此,客户端需要回消息告诉服务端。我接收到了你发来的贺卡,同时我也要给你发回一张好人卡,让你知道你追我的结果的(哎呀,有跑偏了!!!!)。所以,双方之间,都有发起通信的需求,因此才需要双方都保留InputChannel对象,便于发起通信。
  • 因此,说到这里,有一点要说明的:我们前面提到的底层是客户端,上层是服务端,其实也可以反过来,上层变为服务端,底层去做客户端。因为Socket通信,本就没有强调谁一定是服务端、客户端的讲究。

InputChannel实现回调:

  • InputChannel的通信对象都建立完毕了,但是客户端怎么接收事件,怎么回调的逻辑还没有处理,应该在哪里进行呢?

创建InputEventReceiver:

  • InputEventReceiver这个类,看名字就大概知道是用作事件接收作用的。
        public InputEventReceiver(InputChannel inputChannel, Looper looper) {//构造中传入InputChannel,还有Looper
            if (inputChannel == null) {
                throw new IllegalArgumentException("inputChannel must not be null");
            }
            if (looper == null) {
                throw new IllegalArgumentException("looper must not be null");
            }

            mInputChannel = inputChannel;
            mMessageQueue = looper.getQueue();
            mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                                      inputChannel, mMessageQueue);

            mCloseGuard.open("dispose");
        }
  • InputChannel:InputChannel对象和InputEventReceiver建立连接,当InputChannel接收到事件数据的时候,回调InputEventReceiver。
  • Looper:Looper和Handler肯定有莫大关系了。
  • 后续我们再来聊一聊Handler的实现。这里用到Looper,肯定和消息循环有关了。简单说就是用户的输入事件,短期内可能会出现井喷的状况。此时就会出现事件积压的问题,一下子从底层传上来很多事件。
  • 同时也可能出现长时间没有事件上来的可能性。这种不确定性,就让我们需要在短时间内,能够将事件池中的很多事件逐一逐一传递出去的处理能力。这时候,用这种队列方式就很合理了。
  • 当InputChannel接收到事件时,会通知回调InputEventReceiver中的对应接口:
        public void onInputEvent(InputEvent event, int displayId) {
            finishInputEvent(event, false);
        }
  • 因此,如果要处理该InputChannel的事件,只需要实现InputEventReceiver的对应接口,处理对应的InputEvent就可以了。
  • finishInputEvent:
    • 之前说到客户端接收到服务端的消息之后,需要通知服务端,finishInputEvent()这个函数就是完成这项工作的。

回调实现过程:

  • ViewRootImpl中调用WMS的addToDisplay之后,获取到自己的InputChannel,就可以创建InputEventReceiver了。
        WindowInputEventReceiver mInputEventReceiver;
        if (mInputChannel != null) {
            ...
            mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                                                               Looper.myLooper());
        }
  • 创建InputEventReceiver的实现类是WindowInputEventReceiver,这是ViewRootImpl中的一个内部类,继承自InputEventReceiver。
  • 很明显,ViewRootImpl的InputChannel接收到事件后,交给了WindowInputEventReceiver对象处理。
  • 也就是说,窗口的事件,在客户端这边,可以理解为源头就在这里。那我们来看下这个类的onInputEvent()接口
        @Override
        public void onInputEvent(InputEvent event, int displayId) {
            enqueueInputEvent(event, this, 0, true);//看名字,是从事件队列中提取事件进行处理的意思
        }
  • WindowInputEventReceiver的回调,又重新回到了ViewRootImpl的一个函数中:enqueueInputEvent

总结:

  • 从上面的整个流程下来,可以看到,Android在Java端的事件传递流程,就是通过InputChannel来进行实现的。
  • InputChannel就像是一条水管,客户端和服务端分别是水管的两端,事件就是通过InputChannel这条水管,相互之间进行通信。
  • 从底层驱动读取到用户的操作,随后通过InputReader、InputDispatcher进行封装分发,穿越JNI层,到达Java层的InputChannel类。紧接着回调InputEventReceiver,传到了ViewRootImpl中。
  • 整个流程看着十分清晰,后续的事件就通过这个管道源源不断地流向了正确的目标,事件完美驱动起来。
  • 接下来,事件传到了ViewRootImpl中以后,还要经历一系列的分发,如View、ViewGroup、输入法等是否要处理,还有很长的路要走,我们下一篇文章在慢慢探讨

 评论