android input子系统详解

  Input子系统是一个庞大的系统,为了简单明了的介绍该系统,我们采用分模块以及倒序追踪的方式来一步步解开它的面纱。  

一、java层事件传递过程               


我们从这个button被点击来研究input子系统中事件的传递。

废话不多说,对button加一个OnTouchListener,在其onTouch方法上加一个断点,直接利用eclipsedebug工具查看touch事件的传递过程(倒序)

MainActivity$1.onTouch(View, MotionEvent) line: 24
Button(View).dispatchTouchEvent(MotionEvent) line: 8582
RelativeLayout(ViewGroup).dispatchTransformedTouchEvent(MotionEvent, boolean, View, int) line: 2519
RelativeLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 2110
FrameLayout(ViewGroup).dispatchTransformedTouchEvent(MotionEvent, boolean, View, int) line: 2519
FrameLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 2110
ActionBarOverlayLayout(ViewGroup).dispatchTransformedTouchEvent(MotionEvent, boolean, View, int) line: 2519
ActionBarOverlayLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 2110
PhoneWindow$DecorView(ViewGroup).dispatchTransformedTouchEvent(MotionEvent, boolean, View, int) line: 2519
PhoneWindow$DecorView(ViewGroup).dispatchTouchEvent(MotionEvent) line: 2110
PhoneWindow$DecorView.superDispatchTouchEvent(MotionEvent) line: 2482
 
PhoneWindow.superDispatchTouchEvent(MotionEvent) line: 1798
 
MainActivity(Activity).dispatchTouchEvent(MotionEvent) line: 2797
 
PhoneWindow$DecorView.dispatchTouchEvent(MotionEvent) line: 2443
PhoneWindow$DecorView(View).dispatchPointerEvent(MotionEvent) line: 8799
 
ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl$QueuedInputEvent) line: 4663
ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl$QueuedInputEvent) line: 4521
ViewRootImpl$ViewPostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) line: 4035
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$QueuedInputEvent) line: 4088
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$QueuedInputEvent) line: 4054
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$AsyncInputStage).forward(ViewRootImpl$QueuedInputEvent) line: 4191
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).apply(ViewRootImpl$QueuedInputEvent, int) line: 4062
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$AsyncInputStage).apply(ViewRootImpl$QueuedInputEvent, int) line: 4248
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) line: 4035
ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$QueuedInputEvent) line: 4088
ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$QueuedInputEvent) line: 4054
ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).apply(ViewRootImpl$QueuedInputEvent, int) line: 4062
ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) line: 4035
ViewRootImpl.deliverInputEvent(ViewRootImpl$QueuedInputEvent) line: 6464
ViewRootImpl.doProcessInputEvents() line: 6438
ViewRootImpl.enqueueInputEvent(InputEvent, InputEventReceiver, int, boolean) line: 6391
ViewRootImpl$WindowInputEventReceiver.onInputEvent(InputEvent) line: 6623
ViewRootImpl$WindowInputEventReceiver(InputEventReceiver).dispatchInputEvent(int, InputEvent) line: 185
MessageQueue.nativePollOnce(long, int) line: not available [native method]
MessageQueue.next() line: 148
Looper.loop() line: 151
ActivityThread.main(String[]) line: 5637
Method.invoke(Object, Object[], boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 372
ZygoteInit$MethodAndArgsCaller.run() line: 959
ZygoteInit.main(String[]) line: 754


下面对这个关系链按着正序进行分析:

首先看一下整个的调用流程图

 

几点说明:

1.InputEventReceiveronInputEvent方法被回调,开启了javatouch事件传递,至于InputEventReceiver是被谁回调的,这里不得而知,我们稍后再分析。

2.InputStage. NativePreImeInputStageViewPreImeInputStageImeInputStageEarlyPostImeInputStageNativePostImeInputStageViewPostImeInputStageSyntheticInputStage构成一个输入事件责任处理链,如果本阶段对事件没有处理,则传递到下一个对象进行处理,直至事件被处理。NativePreImeInputStageViewPreImeInputStageImeInputStage三个类用来实现输入法的按键派发和处理,如果事件不传递到输入法服务中,这三个类可以跳过,直接从EarlyPostImeInputStage对象开始处理,在ViewPostImeInputStage对象处理阶段调用了主View 对象(对应PhoneWindow中的DecorView对象)的事件提交函数如(dispatchTouchEvent)函数向视图对象提交输入事件,在当前窗口的视图树中派发事件。

3.MainActivity.  DecorView在传递事件过程中首先将事件传给ActivitydispatchTouchEvent()方法,所以我们可以重写这个方法来阻止事件继续传递,在手表项目中为了屏蔽应用左滑退出的功能,就采用这种方式做到的。

4.ViewGroup. 这是touch事件在层层viewgroup中传递的过程,如果某个viewgroup拦截了事件,譬如手表中交互是左滑退出,这里面实际上就是SwipeDismissLayout检测到左滑手势时回调OnDismissedListeneronDismissed()方法,最终finish当前activity.

下面是app布局的层级关系hierarchy view

 

这个图里面我们可以看出,decorView是根view,SwipeDismisslayout是怎么进到这里面来的呢?在5.0之后,只要加上<item name="android:windowSwipeToDismiss">true</item>,我们的应用就有左滑推出的功能。

那到底framework是在哪里识别这个标签的呢?

 

这是一个app创建布局的过程,不细说了。其中generatelayout这个方法就是在加载我们写的布局文件之前根据feature添加一些中间的viewgroup,譬如SwipeDismisslayout.

再比如下面是一个带actionbar的应用的hierarchy view,这是多么的复杂啊,实际上我们添加的只是最后的Button,framework给我们追加了一个复杂的ActionBarOverlayLayout.

 

 

5.touch事件在ViewGroup中传递细节:

 

这里面需要注意几点:

i.action_down事件,如果onInterceptTouchEvent返回trueviewgroup就会拦截touch事件,由自己的onTouchEvent方法处理,并且之后的事件不经过onInterceptTouchEvent,直接由onTouchEvent处理

ii. action_down事件,如果子viewonTouchEvent返回false,那么之后的事件就不会再给这个子view,viewgroup自己的onTouchEvent处理

产生上面两点的原因是mFirstTouchTarget==null,mFirstTouchTarget可以看成是每次action_down开始接收事件的target,即现在viewGroup没有找到事件接受者,只能自己处理,intercepted = true

dispatchTouchEvent(MotionEvent ev) {
..............
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
...............
}

iii.isTransformedTouchPointInView判断touch事件是否在某个view控件范围之内,只是简单的比较x,y座标

    protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }
    /**
     * Determines whether the given point, in local coordinates is inside the view.
     */
    /*package*/ final boolean pointInView(float localX, float localY) {
        return localX >= 0 && localX < (mRight - mLeft)
                && localY >= 0 && localY < (mBottom - mTop);
    }



6.多点触控:

   这里主要说一下MotionEventAction是怎么存储多点信息的

       mAction的低8位(也就是0-7位)是动作类型信息。

       mAction8-15位呢,是触控点的索引信息。(即表示是哪一个触控点的事件)。

 这是一个高效的存储方式,不但记录的事件类型,还说明了是哪个触摸点的事件。

所以我们可以看到MotionEvent有两个方法,一个是getAction(),另一个是getActionMasked(),实际上第二个方法就是通过掩码的方式隐去触控点的索引信息,只记录事件类型

/**
     * Bit mask of the parts of the action code that are the action itself.
     */
    public static final int ACTION_MASK             = 0xff;
public final int getActionMasked() {
        return mAction & ACTION_MASK;
}

我们再来看一下如何得索引值呢?这也是一个利用位运算的高效写法,可以学习一下

Public final int getActionIndex() {
    return  (mAction & 0xff00) >> 8;
}

好了,以上就是事件在java层的传递过程,下面我们分析c++

 

二.C++层事件处理

      在之前java层的事件传递流程中,我们看到最开始的时候是InputEventReceiveronInputEvent方法被回调,那这个方法是被谁调用的呢?带着这个疑问我们进入C++层探寻。

 

这是InputEventReceiver初始化流程,包括java和c层,我们看到最后Looper监听一个文件描述符,(代码位于frameworks/base/core/jni/android_view_InputEventReceiver.cpp):

 int fd = mInputConsumer.getChannel()->getFd();
  mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);

这里是epoll机制(关于Looper中的epool原理,可参考Looper中的epool机制),至于这个监听的fd是何方神圣现在还不知道,但是addFd的第四个参数就是监听到事件之后的回调,果然在NativeInputEventReceiver中实现了handleEvent方法,查看该方法实现:

1.NativeInputEventReceiver::handleEvent(171)  status_t status = consumeEvents(env, false , -1, NULL);
2.NativeInputEventReceiver::consumeEvents(313)
   <span style="font-size:12px;">env->CallVoidMethod(receiverObj.get(),gInputEventReceiverClassInfo.dispatchInputEvent,seq, inputEventObj);</span>

  这里就是native层调用java层代码,即调用InputEventReceiver.java的dispatchInputEvent方法

    private void dispatchInputEvent(int seq, InputEvent event) {
        .......
        onInputEvent(event);//到这里就是我们开始需要的入口点了,终于找到了 不容易
}

至此,我们终于知道onInputEvent是被谁一步步回调的了。

这里我们先不追踪刚才那个被监听的fd从哪里来,先对以上过程做一个总结:

1.Activity初始化的过程中,ActivityThread会通过android.view.WindowManagerImpl类为该Activity创建一个ViewRoot实例,并且会通过调用ViewRoot类的setView成员函数把与该Activity关联的View设置到这个ViewRoot中去,而setView逻辑中会初始化一个InputEventReceiver作为input事件的客户端接受者,InputEventReceiver初始化的过程会在c++层初始化一个NativeInputEventReceiver对象,而在NativeInputEventReceiver初始化的时候,会通过传递来的Looper(客户端主线程)监听一个输入事件的fd(epoll监听机制),当输入事件发生的时候会回调handleEvent方法,最终回调到InputEventReceiver的onInputEvent方法将事件传递给java层。

2.InputEventReceiver的onInputEvent方法被回调之后,会首先将事件传给InputStage责任链,这里面会判断要不要传递给输入法服务。如果只是motionEvent,就会首先传递给PhoneWindow的DecorView,DecorView会把这个事件传给phonewindow的callback也就是当前activity,activity会首先把事件再次传递给phoneWindow,接着phoneWindow就让事件在以DecorView为根的viewGroup中层层传递,直到事件最终被view处理。当然如果我们重写activity的dispatchTouchEvent方法,事件就到不了viewgroup中了。


 

三、输入事件Fd究竟是何方神圣

     在上一步中,

int fd = mInputConsumer.getChannel()->getFd();
mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);

   我们知道这里的fd是关键,为什么监听这个fd就可以拿到输入事件呢?为了解决这个疑问,我们再次采用追本溯源的方式,一步步找到这个fd的源头。经过追踪,发现源头还是在ViewRootImpl的setView方法中:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
.................................
mInputChannel = new InputChannel();
......................
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());
..........................
}


很明显,在上一步中我们已经知道这是InputEventReceiver的初始化源头,之后会进入InputEventReceiver的native层的初始化流程,并最终通过Looper监听fd来最终监听输入事件。而这里的mInputChannel很明显就是int fd = mInputConsumer.getChannel()->getFd()中我们要找的InputChannel. ok,这里这个InputChannel的源头我们找到了,但是更大的疑问来了,明明这里的mInputChannel只是一个简单的初始化,并没有监听什么端口之类的,为什么能在它哪里监听到输入事件呢?只能是之后我们对mInputChannel又做了某些赋值处理,所以,我们继续追踪setView这个方法,果不其然发现了

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), 
mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);

这里mWindowSession是WindowManagerService的远程代理,最终实际上会调到WindowManagerService的addWindow方法中去,而在这个方法里就有对mInputChannel进行真正赋值的操作,下面我们来看addWindow方法的逻辑:

 

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) {
                ................
1                String name = win.makeInputChannelName();
2                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
3                win.setInputChannel(inputChannels[0]);
4                inputChannels[1].transferTo(outInputChannel);
5                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
                ..............................
}

           

这里简单说明一下:
              第2行是创建一对InputChannel,我们大致可以想象一下,一个用于server端的写,一个用于client端的读
              第3行是server端赋值持有相关InputChannel,最终赋值给c++层的InputDispatcher中的InputPublisher                      
              作为向client写事件的入口socket
              第4行,将创建的InputChannel对中的一个赋值给client端的InputChannel
              第5行实际上最终是在InputDispatcher中通过Looper监听client端返回的事件处理完成的信号,从而完成 
              整个事件处理链条。

           我们先来分析InputChannel.openInputChannelPair的过程:

                  最终调用到Jni层的android_view_InputChannel_nativeOpenInputChannelPair

static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
        jclass clazz, jstring nameObj) {
    const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
    String8 name(nameChars);
    env->ReleaseStringUTFChars(nameObj, nameChars);
    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
            new NativeInputChannel(serverChannel));
    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
            new NativeInputChannel(clientChannel));
    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
    return channelPair;
}


           这里调用InputChannel::openInputChannelPair创建了server端和client端的通道,然后调用                           android_view_InputChannel_createInputChannel将native层InputChannel对象转换为Java层InputChannel对象,我们看一下InputChannel::openInputChannelPair的具体实现

 

status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        status_t result = -errno;
        ALOGE("channel '%s' ~ Could not create socket pair.  errno=%d",
                name.string(), errno);
        outServerChannel.clear();
        outClientChannel.clear();
        return result;
    }
 
    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
 
    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);
 
    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel= new InputChannel(clientChannelName, sockets[1]);
    return OK;
}


           这里我们发现建立了一对匿名的已经连接的套接字socket,同时outServerChannel和outClientChannel分别持有

           这对套接字的两端,这样就实现了server端写入输入事件能被client端读到。到这里我们就明白了client端对

           输入事件的监听最终是监听socket。

    

          再看另一个问题,就是之前WindowManagerService.addWindwo方法中:

            inputChannels[1].transferTo(outInputChannel);这实际上是将生成的outClientChannel赋值给outInputChannel,但这里并没有赋值给client端的InputChannel啊?这里到底是怎么影响到client端的InputChannel呢?

这里实际上是利用binder调用中out关键字,我们来看一下IWindowSession.aidl中的addToDisplay方法声明:

 int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,
            in int viewVisibility, in int layerStackId, out Rect outContentInsets,
            out Rect outStableInsets, out InputChannel outInputChannel);

  很明显这里使用了out关键字,对binder调用熟悉的同学就知道实际上这个关键字的作用就是我们传递给server端的参数在server进程中被改变的话会被反馈回给client端。这样就相当于我们在client端可以拿到之前打开的那个socket,最终回到第二部分,就可以被Looper监听这个socket了。          


这里面大致上就把client端的InputChannel的赋值过程给介绍了。这里面还有一个小细节值得介绍一下:

就是我们看到InputChannel.java中有这样一个成员变量:

      @SuppressWarnings("unused")
      private long mPtr; // used by native code

 这里实际上已经注释的很清楚了,就是mPtr是用于java层InputChannel对象和native层的InputChannel对象转换用的,我们来看一下android_view_InputChannel.cpp中将java对象转换为nativie对象的方法

static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env,
        jobject inputChannelObj) {
    jlong longPtr = env->GetLongField(inputChannelObj, gInputChannelClassInfo.mPtr);
    return reinterpret_cast<NativeInputChannel*>(longPtr);
}

           这里我们可以看到实际上mPtr变量就是InputChannel对应的native对象的指针,只需要一个强制类型转换就可以拿到NativeInputChannel*了,是很方便的。这种java变量和native变量的变换在整个android系统中是很普通的。

 

static void android_view_InputChannel_setNativeInputChannel(JNIEnv* env, jobject inputChannelObj,
        NativeInputChannel* nativeInputChannel) {
    env->SetLongField(inputChannelObj, gInputChannelClassInfo.mPtr,
             reinterpret_cast<jlong>(nativeInputChannel));
}

四、server端对输入事件的监听

     在第三部分分析中,我们知道会创建一对持有连接好的socket的InputChannel分别交给client端和server端,server端对socket写入事件时,会被client端监听到从而将输入事件传递给上层IME输入法或View、Activity.那么现在问题来了,server端是如何获取到输入事件的呢,又是在哪里往这个socket里写入事件的呢?这就来到了input子系统最复杂的一块了,InputManagerService的初始化过程、以及对输入事件的轮询和分发过程。这里面我们分两部分讲解:

1.InputManagerService的初始化过程

   

      这是InputManagerService的初始化流程,最终InputManager中会初始化两个thread,InputReaderThread和DispatcherThread。这两个thread会loop起来,其中InputReaderThread会wait在EventHub->getEvents这里等待下面上报输入事件;而DispatcherThread会mLooper->pollOnce(),睡眠在这里,等待被InputReaderThread唤醒mLooper->wake().

EventHub是输入设备的控制中心,它直接与input driver打交道。负责处理输入设备的增减,查询,输入事件的处理并向上层提供getEvents()接口接收事件。在它的构造函数中,主要做三件事:
1. 创建epoll对象,之后就可以把各输入设备的fd挂在上面多路等待输入事件。
2. 建立用于唤醒的pipe,把读端挂到epoll上,以后如果有设备参数的变化需要处理,而getEvents()又阻塞在设备上,就可以调用wake()在pipe的写端写入,就可以让线程从等待中返回。
3. 利用inotify机制监听/dev/input目录下的变更,如有则意味着设备的变化,需要处理。

 

2.Server端对输入事件的轮询和分发过程

  

首先通过EventHub的getEvents函数读取输入设备事件,经过处理后从事件中获得对应的deviceId,根据deviceId在mDevices数组中找到对应的输入设备对象或者构造新的InputDevice对象,然后调用InputDevice的process函数。

在InputDevice的process函数中对每个事件由InputDevice对象的mMappers数组中登记的每个InputMapper对象依次处理,分别调用其process函数,对原始输入事件进行映射。

 不同的事件类型采用了不同的具体InputMapper对象进行映射,如按键事件对应的是KeyboadInputMapper对象,另外还有TouchInputMapper、CursorInputMapper、VibratorInputMapper、SwitchInputMapper、JoystickInputMapper等映射对象,这些对象的类都是InputMapper虚拟类的具体类。

  一个支持多种类型的事件输入的输入设备,需要使用多个不同的输入映射对象分别进行映射。

 在InputMapper对象的process函数中把事件封装成NotifyArgs对象,然后调用InputMapper的监听对象QueedInputListener的相关事件监听接口函数,如notifyKey函数。

在QueedInputListener的监听接口函数中作为参数传进来的NotifyArgs对象放入QueedInputListener的事件队列ArgsQueue中,然后返回并在loopOnce函数中调用QueuedListener对象的flush函数。在flush函数中依次调用ArgsQueue队列中由事件封装成的NotifyArgs对象的notify函数,notify函数的参数也是一个监听对象,在QueuedInputListener对象实例化时赋值,对应的是一个InputDispatcher对象。这里面的QueuedInputListener是在InputReader初始化时赋值的,同样InputDispatcher作为QueuedInputListener的构造参数也是这时候传进来的。NotifyArgs对象的notify函数调用其参数引用对象(InputDispatcher)的事件通知函数,以NotifyArgs对象为参数,如按键事件对应的notifyKey。 

InputDispatcher对象的事件通知函数根据作为参数传进来的事件构造一个EventEntry对象,然后调用enqueueInboundEventLocked函数把构造的EventEntry事件对象放入内部事件队列中(mInboundQueue),最后调用Looper的wake函数,唤醒事件提交线程即InputDispatcherThread线程来进行事件向管道中的提交。 

InputReader读取的输入事件作为参数通过InputListenerInterface的接口函数被InputDispatcher接收。接收的事件构造为EventEntry对象放入InputDispatcher对象的EventEntry类型的队列mInboundQueue中,并唤醒InputDispatcherThread线程。InputDispatcherThread线程不断从该队列中读取输入事件。首先调用InputDispatcher对象的dispatchOnce函数;dispatchOnce函数又调用dispatchOnceInnerLocked函数,在dispatchOnceInnerLocked函数中进行一系列判断后若是按键事件则调用dispatchKeyLocked函数;在dispatchKeyLocked函数中使用findFocusedWindowTargetsLocked函数寻找焦点窗口,并把找到的焦点窗口通过调用函数addWindowTargetLocked放入mCurrentInputTargets数组中,然后调用addMonitoringTargetsLocked为刚才找到的焦点窗口绑定一个inputChannel通道,接着调用dispatchEventLocked函数;在dispatchEventLocked函数中首先根据刚才焦点窗口绑定的inputChannel找到对应的一个Connection对象,然后调用prepareDispatchCycleLocked;在prepareDispatchCycleLocked函数中调用enqueueDispatchEntryLocked函数,在enqueueDispatchEntryLocked函数中根据传进来的事件对应的对象eventEntry构造一个 DispatchEntry对象,DispatchEntry对象中包含inputTarget的信息,并放入connection对象outboundQueue队列;接着又调用startDispatchCycleLocked;

 在startDispatchCycleLocked函数中通过connection对象中的inputPublisher对象引用调用inputPublisher对象的publishKeyEvent函数或publishMotionEvent函数向客户端发送事件,最终通过InputChannel中socket写给client处理,这就解决了第三部分提出的问题。

 

anr流程

int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
        const EventEntry* entry,
        const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t* nextWakeupTime, const char* reason) {
.................................
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
mInputTargetWaitTimeoutTime = currentTime + timeout;
}
.......................
if (currentTime >= mInputTargetWaitTimeoutTime) {
        onANRLocked(currentTime, applicationHandle, windowHandle,
                entry->eventTime, mInputTargetWaitStartTime, reason);

        // Force poll loop to wake up immediately on next iteration once we get the
        // ANR response back from the policy.
        *nextWakeupTime = LONG_LONG_MIN;
        return INPUT_EVENT_INJECTION_PENDING;
    } else {
        // Force poll loop to wake up when timeout is due.
        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
            *nextWakeupTime = mInputTargetWaitTimeoutTime;
        }
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

 

按键事件的拦截

 按键事件的拦截请求包括队列前的事件拦截(譬如keycode_power)和提交前的拦截(譬如keycode_home),下面以按键事件的拦截为例进行说明。

 对于队列前的按键事件拦截请求,在InputDispatche对象的notifyKey函数和injectInputEvent函数提交输入按键事件到队列之前,通过调用InputDispatcherPolicyInterface接口对象的interceptKeyBeforeQueueing函数发送事件的队列之前拦截请求;而对于事件提交前的按键事件拦截通过调用InputDispatcherPolicyInterface接口对象的interceptKeyBeforeDispatching回调函数来发送其拦截请求,其流程如下:

 在InputDispatche对象的事件提交函数dispatchKeyLocked中判断事件的policyFlags是否含有POLICY_FLAG_PASS_TO_USER,如果含有POLICY_FLAG_PASS_TO_USER就调用InputDispatche对象的postCommandLocked函数,postCommandLocked的参数为doInterceptKeyBeforeDispatchingLockedInterruptible函数指针。在postCommandLocked函数中构造一个CommandEntry对象,其command方法为参数传进来的函数指针,并放入mCommandQueue队列,然后函数dispatchKeyLocked返回,事件在事件提交之前被拦截不再继续派发。

        在函数dispatchKeyLocked返回到dispatchOnce,继续调用runCommandsLockedInterruptible函数对mCommandQueue队列中的命令对象进行处理,对于mCommandQueue队列中的每一个命令对象执行命令对象的command方法,对于刚才放入的命令实际调用的是作为postCommandLocke参数的InputDispatch:doInterceptKeyBeforeDispatchingLockedInterruptible函数指针;因此doInterceptKeyBeforeDispatchingLockedInterrup

tible函数被调用,在doInterceptKeyBeforeDispatchingLockedInterruptible函数中调用InputDispatcherPolicyInterface接口对象的interceptKeyBeforeDispatching事件拦截回调函数发送事件提交前的拦截请求。

 InputDispatcherPolicyInterface接口对象在InputDispatcher对象构造时把对NativeInputManager对象的引用作为参数传入赋值给InputDispatcher对象的InputDispatcherPolicyInterface接口类型的字段mPolicy,因此上述的对InputDispatcherPolicyInterface接口事件的调用实际调用的是NativeInputManager对象的相应函数。

 而NativeInputManage对象的拦截和过滤回调函数又通过JNI调用JAVA层InputManagerService服务的相应函数,如interceptKeyBeforeDispatching、interceptKeyBeforeQueueing和filterInputEvent函数。

 InputManagerService服务的事件拦截函数实际调用的是WindowManagerCallbacks接口的相应接口函数,而InputMonitor对象是WindowManagerCallbacks接口的实现,因此对WindowManagerCallbacks接口函数的调用就是对InputMonitor对象的相应函数的调用。InputMonitor对象的对应函数又通过WindowManagerService窗口管理服务中的PhoneWindowManager对象引用调用PhoneWindowManager对象的对应函数,最终完成事件的拦截处理。

在client处理完事件之后也是要通知server端的,这也是通过Looper::addFd实现监听的

 

好了,以上就是整个分析过程。最后我们给出input子系统整个clinet端和server端的关系图

 

发布了37 篇原创文章 · 获赞 8 · 访问量 5万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章