快速了解Android6.0系统触摸事件工作原理——InputManagerService

简介

Android的事件处理是在Linux基础上完成的。事件的最开始是由硬件监听到,然后通过硬件分到Kernel,Kernel在分到框架层,最后最后框架层把事件分布到应用程序的UI中。下面就以触摸事件为例进行分析。
当有事件发生的时候,硬件首先监听到,然后InputDriver起作用,而框架层的EventHub类一直在InputDriver中读事件;当EventHub类读到事件之后,EventHub就会通过NativeInputManager类首先分发事件到Java层的InputManager,而Java层的InputManager又会把这个事件分发到服务WindowManagerService(WMS),之后又分发到PhoneWindowManager中,最后分发到ViewRootImpl中,ViewRootImpl将事件分发到应用程序的Activity和View中。
具体流程:首先有一个线程在不断的监听屏幕,一旦有触摸事件,就将其事件捕获;其次,还应该存在某种方式可以找到目标窗口,因为可能有多个APP的多个界面为用户可见,必须确定这个事件究竟通知哪个窗口;最后才是目标窗口如何消费事件的问题。
总体来说,Android事件投递(主要以触摸事件为主)的工作流程分为4个部分,如图所示:
快速了解Android6.0系统触摸事件工作原理——InputManagerService_第1张图片
(1) 采集
即对“硬件源”所产生的原始信息进行收集的过程。它需要Linux内核驱动的支持,Android系统则是通过/dev/input/下的节点来访问当前发生的事件。
(2) 前期处理
上一步采集到的信息为“原始数据”,这其中一部分内容对应程序而言并不是“必须”的,而且格式上也相对烦琐,所以需要先经过前期的提炼和转化。
(3)WMS分配
WindowManagerService是窗口的大主管,同时也是InputEvent的派发者。WMS记录了当前系统中所有窗口的完整状态信息,所以只有它才能判断出应该把事件投递给哪一个具体的应用进程进行处理。其派发策略也因事件类型的不同而有所差异,以触摸消息为例,则要先计算出触摸点落在哪个区域,然后才能传递给相应的窗口单元。
(4)应用程序处理
应用开发人员的工作主要体现在这一部分。经过前面的几个步骤的传递,应用程序端收到的事件已经相对“可理解”“好处理”了,接下来要做的就是充分利用这些事件来实现软件的功能。

1、InputManagerService(IMS)诞生

IMS服务的创建过程是在SystemServer进程启动的时候实例化的,并注册到ServiceManager中去,这个服务对外主要是用来提供一些输入设备的信息的作用。
/frameworks/base/services/java/com/android/server/SystemServer.java

private void startOtherServices() {
    ...
    //新建IMS对象
    inputManager = new InputManagerService(context);
    wm = WindowManagerService.main(context, inputManager,
            mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
            !mFirstBoot, mOnlyCore);
    ServiceManager.addService(Context.WINDOW_SERVICE, wm);
    //将IMS发布给ServiceManager,以便其他人可以访问IMS提供的接口
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
    mActivityManagerService.setWindowManager(wm);
    //设置向WMS发起回调Callback对象
    inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
    //启动IMS
    inputManager.start();
    ...
}

InputManagerService和WindowManagerService有密切的联系,也是触摸事件处理的时候同时涉及两个服务,InputManagerService主要负责事件(以触摸事件为例)的采集,而WindowManagerService负责找到目标窗口。接下来,继续分析InputManagerService如何完成触摸事件的采集。

2、事件的采集

/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp& looper) :
        mLooper(looper), mInteractive(true) {
    ...
    //NativeInputManager创建了EventHub
    sp eventHub = new EventHub();
    //继续创建Native层的InputManager
    mInputManager = new InputManager(eventHub, this, this);
}

这里的EventHub,可以看作是事件集线器,是利用Linux的inotify和epoll机制,对设备节点增删事件以及可读状态的监听,例如,各种触摸、按钮事件等等,主要面向的是/dev/input目录下的设备节点,比如说/dev/input/event0上的事件就是输入事件,通过EventHub的getEvents就可以监听并获取该事件。在InputManager创建的时候,同时也会再创建一个InputReader对象及InputReaderThread Loop线程,这个loop线程的主要作用就是像一台水泵一样,通过EventHub的getEvents获取Input事件。
/frameworks/native/services/inputflinger/InputManager.cpp

InputManager::InputManager(
        const sp& eventHub,
        const sp& readerPolicy,
        const sp& dispatcherPolicy) {
    //创建InputDispatcher,事件分发执行类    
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    //创建InputReader,事件读取执行类
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    //将InputReader与InputDispatcher建立关联
    initialize();
}
void InputManager::initialize() {
    //创建供InputReader运行的线程InputReaderThread
    mReaderThread = new InputReaderThread(mReader);
    //创建供InputDispatcher运行的线程InputDispatcherThread
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

/frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    Vector inputDevices;
    ...
    //监听事件
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    ...
            //处理事件
            processEventsLocked(mEventBuffer, count);
    ...
     //通知派发(对上面mDispatcher的进一步封装)  
    mQueuedListener->flush();
}
bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}

上面代码的流程,输入事件就可以被读取,经过processEventsLocked被初步封装成RawEvent,最后发通知,请求派发消息。

3、派发事件

在新建InputManager的时候,不但创建了一个事件读取线程(InputReaderThread),而且还创建了一个事件派发线程()InputDispatcherThread,虽然也可以直接在读取线程中派发,但是这样肯定会增加耗时,不利于事件的及时读取,因此,事件读取完毕后,直接向派发线程发个通知,请派发线程去处理,这样读取线程就可以更加敏捷,防止事件丢失。
InputReader的mQueuedListener其实就是InputDispatcher对象,因此mQueuedListener->flush()就是通知InputDispatcher事件读取完毕,可以派发事件了, InputDispatcherThread是一个典型Looper线程,基于native的Looper实现了Hanlder消息处理模型,如果有Input事件到来就被唤醒处理事件,处理完毕后继续睡眠等待。
/frameworks/native/services/inputflinger/InputDispatcher.cpp

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}
void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { ...
        //被唤醒 ,处理Input消息
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }
    ...
    } 

    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    //睡眠等待input事件
    mLooper->pollOnce(timeoutMillis);
}

上面代码是派发线程的模型,dispatchOnceInnerLocked是具体的派发处理逻辑,里面有很多的分支,以触摸事件为例:

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
     case EventEntry::TYPE_MOTION: {
        MotionEntry* typedEntry = static_cast(mPendingEvent);
        ...
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }
}
bool InputDispatcher::dispatchMotionLocked(
        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ...
    Vector inputTargets;

    bool conflictingPointerActions = false;
    int32_t injectionResult;
    if (isPointerEvent) {
        //关键点1: 找到目标Window
        injectionResult = findTouchedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
    } else {
        // Non touch event.  (eg. trackball)
        injectionResult = findFocusedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime);
    }
    ...
    //关键点2: 消息派发到目标窗口中
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}

触摸事件会首先通过findTouchedWindowTargetsLocked()函数找到目标Window,进而通过dispatchEventLocked()将消息发送到目标窗口。

4、触摸事件找到对应目标窗口

根据触摸事件的位置及窗口的属性来确定将事件发送到哪个窗口
/frameworks/native/services/inputflinger/InputDispatcher.cpp

int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
        const MotionEntry* entry, Vector& inputTargets, nsecs_t* nextWakeupTime,
        bool* outConflictingPointerActions) {
        ...
        sp newTouchedWindowHandle;
        bool isTouchModal = false;
        //遍历所有窗口
        size_t numWindows = mWindowHandles.size();
        for (size_t i = 0; i < numWindows; i++) {
            sp windowHandle = mWindowHandles.itemAt(i);
            const InputWindowInfo* windowInfo = windowHandle->getInfo();
            if (windowInfo->displayId != displayId) {
                continue; // wrong display
            }

            int32_t flags = windowInfo->layoutParamsFlags;
            if (windowInfo->visible) {
                if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
                    isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
                            | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
                    //找到目标窗口       
                    if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                        newTouchedWindowHandle = windowHandle;
                        break; // found touched window, exit window loop
                    }
                 }
            ...     
}

mWindowHandles代表着所有窗口,findTouchedWindowTargetsLocked的就是从mWindowHandles中找到目标窗口。mWindowHandles是怎么来的呢,窗口增删的时候怎样才能保持最新的?这里就引进来WindowManagerService交互问题了,接着分析:

void InputDispatcher::setInputWindows(const Vector >& inputWindowHandles) {
...
mWindowHandles = inputWindowHandles;
...

WindowManagerService中的InputMonitor,会间接调用InputDispatcher::setInputWindows,这个主要是跟窗口增改删除等逻辑相关。

5、发送事件到目标窗口

找到了目标窗口,事件也封装完毕,剩下的就是通知目标窗口。目前所有的操作都是在SystemServer进程完成的,如何才能通知窗口位于APP端的用户进程呢?接下来Socket的通信方式出场了,它是处理input事件的。Socket是怎么来的呢?在当APP端向WMS请求添加窗口的时候,会伴随着Input通道的创建,窗口的添加一定会调用ViewRootImpl中的setView函数:
/frameworks/native/services/inputflinger/ViewRootImpl.cpp

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                ...
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    //创建InputChannel容器    
                    mInputChannel = new InputChannel();
                }
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //添加窗口,并请求开辟Socket Input通信通道
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } ...
                //监听,开启Input信道
                if (mInputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }
                ...
}                

接着看服务端WMS如何填充:

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
     ...
     if (outInputChannel != null && (attrs.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                String name = win.makeInputChannelName();
                //关键点1创建通信信道
                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
                //本地用
                win.setInputChannel(inputChannels[0]);
                //APP端用
                inputChannels[1].transferTo(outInputChannel);
                //注册信道与窗口
                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
            }
     ...       
}

WMS首先创建socketpair作为全双工通道,并分别填充到Client与Server的InputChannel中去;再让InputManager将Input通信信道与当前的窗口ID绑定,这样就能知道哪个窗口用哪个信道通信了;最后通过Binder将outInputChannel回传到APP端,以下是SocketPair的创建代码:
frameworks/native/libs/Input/tests/InputTransport.cpp

status_t InputChannel::openInputChannelPair(const String8& name,
        sp& outServerChannel, sp& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        status_t result = -errno;
        ...
        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));
    //填充到server inputchannel
    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);
    //填充到client inputchannel
    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

socketpair的创建与访问还是借助文件描述符(Fd),WMS需要借助Binder通信向APP端回传文件描述符,在内核层面实现两个进程的转换,窗口添加成功后,socketpair被创建,被传递到了APP端,但是信道并未完全建立,还需要一个主动的监听,当消息到来的时候是需要通知的。APP端的监听消息的手段是:将socket添加到Looper线程的epoll数组中去,当有消息到来时,Looper线程就会被唤醒,并获取事件内容,通信信道的打开是伴随WindowInputEventReceiver的创建来完成的。
信息到来,Looper根据文件描述符(Fd)找到对应的监听器:NativeInputEventReceiver,并调用handleEvent处理对应事件:
/frameworks/base/native/android/android_view_InputEventReceiver.cpp

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
   ...
   if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
...
}

进一步读取事件,并封装成Java层对象,传递给Java层,进行相应的回调:

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
     ...
     for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;
        //获取事件
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);   
     ...
     //处理touch事件
     case AINPUT_EVENT_TYPE_MOTION: 
                ...
                MotionEvent* motionEvent = static_cast(inputEvent);
                if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                    *outConsumedBatch = true;
                }
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                break;
            }
              ...
              //回调处理函数
               if (inputEventObj) {
                ...
                   env->CallVoidMethod(receiverObj.get(),
                      gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);              
}

最后就是触摸事件被封装成了inputEvent,并通过InputEventReceiver的dispatchInputEvent(WindowInputEventReceiver)进行处理,这里就返回到我们常见的Java世界了。
6、在目标窗口中处理事件
/frameworks/base/core/java/android/view/ViewRootImpl.java

 final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper){ 
            super(inputChannel, looper);
        }

        @Override
        public void onInputEvent(InputEvent event) {
         enqueueInputEvent(event, this, 0, true);
        }
   ...     
  }
  void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
       ...
        if (processImmediately) {
            doProcessInputEvents();
        }
        ...
  }
  void doProcessInputEvents() {
      ...
      deliverInputEvent(q);
      ...
  }
  private void deliverInputEvent(QueuedInputEvent q) {
        ...
        InputStage stage;
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

        if (stage != null) {
            stage.deliver(q);
        } 
        ...
   }
   final class ViewPostImeInputStage extends InputStage {
        ...
        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                // If delivering a new non-key event, make sure the window is
                // now allowed to start updating.
                handleDispatchWindowAnimationStopped();
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                }
                ...
            }
        }
        ...
        private int processPointerEvent(QueuedInputEvent q) {
            ...
            boolean handled = mView.dispatchPointerEvent(event);
            ...
        }
    }     

processPointerEvent()方法通过调用mView的dispatchPointerEvent()方法来做进一步的操作,变量mView是一个DecorView类型的对象,它的父类View来实现这个方法,这个调用最终就会进入它的父类View的dispatchPointerEvent()方法中。
/frameworks/base/core/java/android/view/View.java

public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
}

View的dispatchPointerEvent()方法首先通过MotionEvent的isTouchEvent()方法来判断当前的事件是不是TouchEvent事件,若当前是TouchEvent事件,就用dispatchTouchEvent()方法来做分发事件的处理,这个分发过程分为两种情况:
(1)若目前的View是普通的View,就会调用View.java的dispatchTouchEvent()方法来处理。普通的View,比如TextView里面并没有实现dispatchTouchEvent()方法,由于它又是继承View,所有在View的dispatchPointerEvent()方法中调用dispatchTouchEvent()方法或者进入View的dispatchTouchEvent()方法。
(2)若目前的View是容器类View,比如DecorView,由于它实现了dispatchTouchEvent()方法,所以View的dispatchPointerEvent()方法中调用dispatchTouchEvent()方法就会进入DecorView的dispatchTouchEvent()方法。
这两种分发过程在下篇文章《Android6.0系统View事件分发》中继续分析。
结束!!

你可能感兴趣的:(Android系统源码分析)