Android Input的流程和原理

Android Input事件机制

Android系统是由事件驱动的,而Input是最常见的事件之一,用户的点击、滑动、长按等操作,都属于Input事件驱动,其中的核心就是InputReader和InputDispatcher。InputReader和InputDispatcher是跑在system_server进程中的两个Native循环线程,负责读取和分发Input事件。整体处理流程大致如下:

  1. 触摸屏会按照屏幕硬件的触控采样率周期,每隔几秒扫描一次,如果有触控时间就会上报到对应设备驱动;系统封装了一个叫EventHub的对象,它利用INotify和epoll机制监听/dev/input目录下的Input设备驱动节点,通过EventHug的getEvents接口就可以监听并获取到Input事件;

  2. InputReader负责从EventHub里面把Input事件读取出来,然后交给InputDispatcher进行事件分发;

  3. InputDispatcher在拿到InputReader获取的事件之后,对事件进行包装后,寻找并分发到目标窗口;

  4. InboundQueue队列("iq")中放着InputDispatcher从InputReader中拿到的Input事件;

  5. OutboundQueue队列("oq")里面放的是即将要指派给各个目标窗口App的事件;

  6. WaitQueue队列("wq")里面记录的是已经派发给App,但是App还在处理没有返回处理成功的事件;

  7. PendingInputEventQueue("aq")中记录的是应用需要处理的Input事件,这里可以看到Input事件已经传递到应用进程;

  8. deliverInputEvent标识App UI Thread被Input事件唤醒;

  9. InputResponse标识Input事件区域,这里可以看到一个Input_Down事件 + 若干个Input_Move + 一个Input_Up事件的处理阶段都被算到了这里;

  10. App响应处理Input事件,内部会在其界面View数中逐层分发和处理。

 

结合Systrace分析

从上面的系统机制的分析可以看出,整个Input触控时间的分发与处理主要涉及到两个进程:一个是system_server系统进程,另一个是当前焦点窗口所属的Setting应用进程。

system_server进程的处理过程

  1. 当用户手指在Setting应用界面滑动时,系统system_server进程中的Native线程InputReader会从EventHub中读取,利用Linux的epoll机制监听到的屏幕驱动上报的Input触控事件,然后唤醒另一条Native线程InputDIspatcher负责进行事件的进一步分发处理。

  2. InputDispatcher被唤醒后会先将事件放到InboundQueue队列("iq")中,然后找到具体处理此Input事件的应用目标窗口,并将Input事件放入对应的应用目标窗口的OutboundQueue队列("oq")中,等待进一步通过SocketPair双工信道发送Input事件到应用目标窗口中。

  3. 最后当事件发送给具体的应用目标窗口后,会将事件移动到WaitQueue队列("wq")中,一直等待收到目标应用处理Input事件完成后反馈后再从队列中移除,如果5秒钟没有收到目标应用窗口处理完成此次Input事件的反馈,就会报该应该ANR异常事件。以上整个过程在Android系统AOSP源码中都加有相应的Systrace tag,如下图Systrace所示:

应用进程的处理过程

当Input触控时间通过socket传递到Setting应用进程这边后,会唤醒应用的UI线程在ViewRootImpl#deliverInputEvent的流程中进行Input事件的具体分发与处理。具体的处理流程:

  1. 先交给之前在添加应用PhoneWindow窗口时的ViewRootImpl#setView流程中创建的多个不同类型的InputUsage中依次进行处理(比如对输入法处理逻辑的封装ImeInputUsage,某些key类型的Input事件会由它先交给输入法进程处理完成后再交给应用窗口的InputUsage处理),整个处理流程是按照责任链的设计模式进行;

  2. 然后会交给负责应用窗口Input事件分发处理的ViewPostImeInputUsage中具体处理,这里面会从View布局树的根节点DecorView开始遍历整个View树上的每一个子View或ViewGroup控件执行事件的分发、拦截、处理的逻辑;

  3. 最后触控时间处理完成后会调用finishInputEvent结束应用对触控事件处理逻辑,这里面会通过JNI调用到Native层InputConsumer的sendFinishedSignal函数中通过socket消息通知系统框架中的InputDIspatcher该Input事件处理完成,触发从"wq"队列中及时移除待处理事件以免报ANR异常。

一次滑动过程的触控交互的InputResponse区域中一般会包含一个Input的ACTION_DOWN事件 + 多个ACTION_MOVE事件 + 一个ACTION_UP事件,Setting应用界面中的相关View控件在收到多个ACTION_MOVE触控事件后,经过判断用户手指滑动行为,一般会调用View#invalidate等相关接口触发UI线程的绘制上帧更新画面的操作。

Android Input初始化及InputReader流程

概述

当输入设备可用时,Linux内核会在/dev/input/下创建对应名为event0~n或其他名称的设备节点。而当输入设备不可用时,则会将对应的节点删除。

Android输入系统的工作原理概括来说,就是监控/dev/input/下的所有设备节点,当某个节点有数据可读时,将数据读出并进行一系列的翻译加工,然后再所有的窗户中寻找合适的事件接收者,并派发给它。

getevent与sendevent

getevent与sendevent两个工具可以从设备节点中直接读取输入事件或写入输入事件。

Getevent

由于getevent不会对事件数据做任何加工,因此其输出的内容是由内核提供的最原始的事件,输出是十六进制的:

adb shell getevent [-选项] [device_path]。

adb shell getevent –t 查看当前按下按键的值,值0x01表示按下,0x00则表示抬起。

按下返回

[1556162527.777123] /dev/input/event6: 0001 009e 00000001

[1556162527.777123] /dev/input/event6: 0000 0000 00000000

送开返回

[1556162530.504152] /dev/input/event6: 0003 0030 00000000

[1556162530.504152] /dev/input/event6: 0003 0032 00000000

[1556162530.504152] /dev/input/event6: 0000 0002 00000000

[1556162530.504152] /dev/input/event6: 0000 0000 00000000

[1556162530.525527] /dev/input/event6: 0001 009e 00000000

[1556162530.525527] /dev/input/event6: 0000 0000 00000000

事件类型(0001),事件代码(009e)以及事件的值(00000001)。

Sendevent

实现模拟用户输入的功能,sendevent的参数为十进制。

sendevent <节点路径> <类型><代码> <值>

adb shell sendevent /dev/input/event0 1 116 1 #按下电源键

adb shell sendevent /dev/input/event0 1 116 0 #抬起电源键

adb shell input keyevent 4 # 返回按键

Android系统Input事件处理流程

内核将原始事件写入到设备节点中,InputReader不断地通过EventHub将原始事件取出来并翻译加工成Android输入事件,然后交给InputDIspatcher。InputDIspatcher根据WMS提供的窗口信息将事件交给合适的窗口。窗口的ViewRootImpl对象再沿着控件树将事件派发给感兴趣的控件。控件对其收到的事件做出响应,更新自己的画面、执行特定的动作。

  1. Linux内核,接收输入设备的中断,并将原始事件的数据写入到设备节点中。

  2. 设备节点,作为内核的IMS的桥梁,它将原始事件的数据暴露给用户控件,以便IMS可以从中读取事件。

  3. InputManagerService,一个Android系统服务,它分为Java层和Native层两个部分。Java层负责与WMS的通信。而Native层则是InputReader和InputDIspatcher两个输入系统关键组件的运行容器。

  4. EventHub,直接访问所有的设备节点。并且正如其名字所描述的,它通过一个名为getEvents()函数将所有输入系统相关的待处理的底层事件返回给使用者。这些事件包括原始输入事件、设备节点的增删等。

  5. InputReader,是IMS中的关键组件之一。它运行于一个独立的线程中,负责管理输入设备的列表与配置,以及进行输入事件的加工处理。它通过其线程循环不断地通过getEvent()函数从EventHub中将事件取出并进行处理。对于设备节点的增删事件,它会更新输入设备列表于配置。对于原始输入事件,InputReader对其进行翻译、组装、封装为包含了更多信息、更具可读性的输入事件,然后交给InputDIspatcher进行派发。

  6. InputReaderPolicy,它为InputReader的事件加工处理提供一些策略配置,例如键盘布局信息等。

  7. InputDispatcher,是IMS中另一个关键组件。它也运行于一个独立的线程中。InputDIspatcher中保管了来自WMS的所有窗口的信息,其收到来自InputReader的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口。

  8. InputDIspatcherPolicy,它为InputDIspatcher的派发过程提供策略控制。例如截取某些特定的输入事件来作特殊用途,或者阻止将某些事件派发给目标窗口。一个典型的例子就是HOME键被InputDIspatcherPolicy截取到PhoneWindowManager中进行处理,并阻止窗口收到HOME键按下的事件。

  9. WMS,虽然不是输入系统中的一员,但是它却对InputDIspatcher的正常工作起到了至关重要的作用。当新建窗口时,WMS为新窗口和IMS创建了事件传递所用的通道。另外,WMS还将所有窗口的信息,包括窗口的可点击区域,焦点窗口等信息,实时地更新到IMS的InputDIspatcher中,使得InputDIspatcher可以正确的将事件派发到指定的窗口。

  10. ViewRootImpl,对于某些窗口,如壁纸窗口、SurfaceView的窗口来说,窗口即是输入事件派发的终点。而对于其他的如Activity、对话框等使用了Android控件系统的窗口来说,输入事件的终点是控件(View)。ViewRootImpl将窗口所收到的输入事件沿着控件树将事件派发给感兴趣的控件。

初始化

InputManagerService初始化

这段代码是Android系统中InputManagerService的启动过程。首先,在SystemServer服务的startOtherServices()方法中,创建并启动了InputManagerService实例。然后,在InputManagerService的构造函数中,初始化了Native层和Java层的通信,并将Java层的InputManagerService对象和消息队列的Looper传递给Native层。最后,在nativeInit方法中,创建了一个NativeInputManager对象,这个对象是Java层和Native层之间的桥梁,并将这个对象的指针返回给Java层的InputManagerService,保存在mPtr成员变量中。

具体来说,startOtherServices()方法中:

  1. 创建了InputManagerService实例。

  2. 调用了InputManagerService的setWindowManagerCallbacks()方法,将WindowManager的回调传递给InputManagerService。

  3. 调用了InputManagerService的start()方法,启动了InputManagerService。

InputManagerService的构造函数中:

  1. 保存了上下文信息。

  2. 创建了一个名为InputManagerHandler的Handler,用于处理输入事件。

  3. 调用了native方法nativeInit(),传入了上下文对象、InputManagerService对象和消息队列的Looper。

  4. 将InputManagerService注册为InputManagerInternal服务的本地服务。

nativeInit()方法中:

  1. 从Java层的消息队列对象中获取了消息队列。

  2. 创建了一个NativeInputManager对象,这个对象是Java层和Native层之间的桥梁。

  3. NativeInputManager对象的指针转换为jlong类型,并返回给Java层的InputManagerService,保存在mPtr成员变量中。

这样,Java层的InputManagerService就可以通过mPtr成员变量与Native层的NativeInputManager进行通信了。这个过程是Android系统启动过程中的一部分,用于初始化和管理输入事件,包括触摸、按键等输入事件的处理。

* frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
        ...
         traceBeginAndSlog("StartInputManagerService");
     inputManager = new InputManagerService(context);
     traceEnd();
     ...
     traceBeginAndSlog("StartInputManager");
     inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
     inputManager.start();
     traceEnd();
     ...      
}

* frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public InputManagerService(Context context) {
        this.mContext = context;
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
                ...
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
                ...
        LocalServices.addService(InputManagerInternal.class, new LocalService());
}

* frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
   ...
   /* 新建了一个NativeInputManager对象,NativeInputManager,
   此对象将是Native层组件与Java层IMS进行通信的桥梁 */
    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,  messageQueue->getLooper());
    im->incStrong(0);
    //返回了NativeInputManager对象的指针给Java层的IMS,IMS将其保存在mPtr成员变量中
    return reinterpret_cast<jlong>(im);
}

在nativeInit函数中,将Java层的MessageQueue转换为native层的MessageQueue,然后再取出Looper用于NativeInputManager的初始化。NativeInputManager是Java层与Native层互相通信的桥梁,它实现了InputReaderPolicyInterface与InputDispatcherPolicyInterface两个接口,通过JNI回调Java层的IMS,由它完成决策。

NativeInputManager的初始化

这个过程做了以下事情:

将Java层的InputManagerService转换为native层的InputManagerService存储在mServiceObj中 -创建InputManager。

这段代码是Android系统中InputManager的初始化过程。在InputManager的构造函数中,首先创建了InputDispatcher和InputClassifier对象,这两个对象分别负责分发输入事件和输入事件的分类。然后,创建了InputReader对象,这个对象负责从设备读取输入事件。最后,创建了InputReaderThread和InputDispatcherThread两个线程,分别用于处理输入事件的读取和分发。

具体来说:

  1. InputManager的构造函数中:

    1. 创建了InputDispatcher对象,用于分发输入事件。

    2. 创建了InputClassifier对象,用于对输入事件进行分类。

    3. 调用createInputReader()方法创建了InputReader对象,这个对象负责从设备读取输入事件。

    4. 调用initialize()方法,创建了InputReaderThreadInputDispatcherThread两个线程。

  2. createInputReader()方法中:

    1. 创建了InputReader对象,这个对象负责从设备读取输入事件。

  3. initialize()方法中:

    1. 创建了InputReaderThread对象,这个线程用于处理输入事件的读取。

    2. 创建了InputDispatcherThread对象,这个线程用于处理输入事件的分发。

这样,InputManager就可以通过这两个线程来处理输入事件了。

这个过程是Android系统启动过程中的一部分,用于初始化和管理输入事件,包括触摸、按键等输入事件的处理。

* frameworks/native/services/inputflinger/InputManager.cpp
InputManager::InputManager(
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mClassifier = new InputClassifier(mDispatcher);
    mReader = createInputReader(readerPolicy, mClassifier);
    initialize();
}

sp<InputReaderInterface> createInputReader(
        const sp<InputReaderPolicyInterface>& policy,
        const sp<InputListenerInterface>& listener) {
    return new InputReader(new EventHub(), policy, listener);
}

void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

InputManager的初始化

InputManager的构造函数也比较简洁,它创建了四个对象,分别为IMS的核心参与者InputReader与InputDispatcher,以及它们所在的线程InputReaderThread与InputDispatcherThread。注意InputManager的构造函数的参数readerPolicy与dispatcherPolicy,它们都是NativeInputManager。

InputDispatcher会创建自己线程的Looper,以及设置根据传入的dispatchPolicy设置分发规则。InputReader则会将传入的InputDispatcher封装为监听对象存起来,并创建一个EventHub。

启动

system_server执行InputManagerService.start()函数以启动IMS,在start()方法中,做了以下事情:

  1. 调用nativeStart方法,其实就是调用InputManager的start()方法。

  2. 将InputManagerService交给WatchDog监控。

  3. 注册触控点速度、显示触控的观察者,并注册广播监控它们。

  4. 主动调用updateXXX方法更新(初始化)。

InputManager的start()启动InputDispatcherThread和InputReaderThread开始监听。

当两个线程启动后,InputReader在其线程循环中不断地从EventHub中抽取原始输入事件,进行加工处理后将加工所得的事件放入InputDispatcher的派发发队列中。InputDispatcher则在其线程循环中将派发队列中的事件取出,查找合适的窗口,将事件写入到窗口的事件接收管道中。窗口事件接收线程的Looper从管道中将事件取出,交由事件处理函数进行事件响应。

这段代码是Android系统中InputManagerService的启动过程。在Java层的start()方法中,调用了native方法nativeStart(),传入了mPtr成员变量,这个变量是Java层和Native层之间的桥梁。

在Native层的InputManagerstart()方法中,首先启动了InputDispatcherThread线程,这个线程负责分发输入事件。然后,启动了InputReaderThread线程,这个线程负责从设备读取输入事件。

具体来说:

  1. InputManagerServicestart()方法中:

    1. 调用了native方法nativeStart(),传入了mPtr成员变量,这个变量是Java层和Native层之间的桥梁。

  2. InputManagerstart()方法中:

    1. 调用mDispatcherThread->run()方法,启动了InputDispatcherThread线程,这个线程负责分发输入事件。

    2. 调用mReaderThread->run()方法,启动了InputReaderThread线程,这个线程负责从设备读取输入事件。

这样,InputManager就可以通过这两个线程来处理输入事件了。

这个过程是Android系统启动过程中的一部分,用于初始化和管理输入事件,包括触摸、按键等输入事件的处理。

* frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
 public void start() {
        Slog.i(TAG, "Starting input manager");
        nativeStart(mPtr);
        ...
        updatePointerSpeedFromSettings();
        updateShowTouchesFromSettings();
        updateAccessibilityLargePointerFromSettings();
}
    
* frameworks/native/services/inputflinger/InputManager.cpp
status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    ...
    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
   ...
}

InputReaderThread

        启动后循环执行mReader->loopOnce(),loopOnce中会调用mEventHub->getEvents读取事件,读取的结果存储在参数mEventBuffer中,返回值表示事件的个数,当EventHub中无事件可以抽取时,此函数的调用将会阻塞直到事件到来或者超时。

        读取到了事件就会调用processEventsLocked处理事件,对于设备事件,此函数对根据设备的可用性加载或移除设备对应的配置信息。对于原始输入事件,则在进行转译、封装与加工后将结果暂存到mQueuedListener中,处理完成后调用getInputDevicesLocked获取输入设备信息。

        调用mPolicy->notifyInputDevicesChanged函数利用InputManagerService的代理通过Handler发送MSG_DELIVER_INPUT_DEVICES_CHANGED消息,通知输入设备发生了变化。

        最后调用mQueuedListener->flush(),将事件队列中的所有事件交给在InputReader中注册过的InputDIspatcher。

        注意 C++层的Thread类与Java层的Thread类有着一个显著的不同。C++层Thread类内建了线程循环,threadLoop()就是一次循环而已,只要返回值为true,threadLoop()将会不断地被内建的循环调用。这也是InputReader.loopOnce()函数名称的由来。而Java层Thread类的run()函数则是整个线程的全部,一旦其退出,线程也便完结。

这段代码是Android系统中InputReader的循环处理过程。在Native层的InputReaderThreadthreadLoop()方法中,调用了InputReaderloopOnce()方法,这个方法会循环读取输入事件。

InputReaderloopOnce()方法中,首先从EventHub中读取输入事件,然后处理这些事件。如果输入设备发生了变化,就通知InputReaderPolicy。最后,调用mQueuedListenerflush()方法,将处理过的输入事件发送给监听者。

具体来说:

  1. InputReaderThreadthreadLoop()方法中:

    1. 调用了mReaderloopOnce()方法,这个方法会循环读取输入事件。

  2. InputReaderloopOnce()方法中:

    1. EventHub中读取输入事件。

    2. 处理这些事件。

    3. 如果输入设备发生了变化,就通知InputReaderPolicy

    4. 调用mQueuedListenerflush()方法,将处理过的输入事件发送给监听者。

这样,InputReader就可以通过这个循环来处理输入事件了。

这个过程是Android系统启动过程中的一部分,用于初始化和管理输入事件,包括触摸、按键等输入事件的处理。

* frameworks/native/services/inputflinger/InputReaderBase.cpp
bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}

* frameworks/native/services/inputflinger/InputReader.cpp
void InputReader::loopOnce() {
    ...
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }
                ...
                if (oldGeneration != mGeneration) {
            inputDevicesChanged = true;
            getInputDevicesLocked(inputDevices);
        }
    } // release lock

    // Send out a message that the describes the changed input devices.
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }
    mQueuedListener->flush();
}

EventHub

InputReader在其线程循环中的第一个工作便是从EventHub中读取一批未处理的事件。 EventHub的直译是事件集线器,顾名思义,它将所有的输入事件通过一个接口getEvents()将从多个输入设备节点中读取的事件交给InputReader,是输入系统最底层的一个组件。

这段代码是Android系统中EventHub的初始化过程。在EventHub的构造函数中,首先创建了一个epoll对象和inotify对象,用于监听设备节点的增删事件。然后,将设备节点的路径/dev/input作为监听对象添加到inotify对象中,当此文件夹下的设备节点发生创建与删除事件时,都可以通过inotify对象读取事件的详细信息。接着,将inotify对象和名为wakeFds的匿名管道(用于唤醒InputReader线程)作为epoll的监控对象,并注册到epoll对象中。

具体来说:

  1. EventHub的构造函数中:

    1. 创建了一个epoll对象。

    2. 创建了一个inotify对象,用于监听设备节点的增删事件。

    3. 将设备节点的路径/dev/input作为监听对象添加到inotify对象中。

    4. 创建了一个名为wakeFds的匿名管道,并将管道读取端的描述符的可读事件注册到epoll对象中。

  2. 在epoll对象中注册了inotify对象和wakeFds管道,用于监听设备节点的增删事件和唤醒InputReader线程。

这样,EventHub就可以通过epoll和inotify来监听设备节点的变化,并及时处理这些事件了。

这个过程是Android系统启动过程中的一部分,用于初始化和管理输入事件,包括触摸、按键等输入事件的处理。

* frameworks/native/services/inputflinger/EventHub.cpp
EventHub::EventHub(void) :
        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
        mOpeningDevices(nullptr), mClosingDevices(nullptr),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false), mNeedToScanDevices(true),
        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
    //创建一个epoll对象
    mEpollFd = epoll_create1(EPOLL_CLOEXEC);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
    //创建一个inotify对象。这个inotify对象将被用来监听设备节点的增删事件
    mINotifyFd = inotify_init();
    //将存储设备节点的路径/dev/input作为监听对象添加到inotify对象中。当此文件夹下的设备节点
    //发生创建与删除事件时,都可以通过mINotifyFd读取事件的详细信息
    mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
   ...
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mINotifyFd;
    //将mINotifyFd作为epoll的一个监控对象。当inotify事件到来时,epoll_wait()将
    //立刻返回,EventHub便可从mINotifyFd中读取设备节点的增删信息,并作相应处理
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);
        
        /*创建了一个名为wakeFds的匿名管道,并将管道读取端的描述符的可读事件注册到epoll对象中。
        因为InputReader在执行getEvents()时会因无事件而导致其线程阻塞在epoll_wait()的调用里,
        然而有时希望能够立刻唤醒InputReader线程使其处理一些请求。
        此时只需向wakeFds管道的写入端写入任意数据,此时读取端有数据可读,使得epoll_wait()得以返回,
        从而达到唤醒InputReader线程的目的*/

    int wakeFds[2];
    result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
        ...
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
    ...
}

INotify

INotify是一个Linux内核所提供的一个文件系统变化通知机制。它可以为应用程序监控文件系统的变化,如文件的创建、删除、读写等。INotify机制有两个基本对象,分别为inotify对象与watch对象,都使用文件描述符表示。

  • inotify对象对应了一个队列,应用程序可以向inotify对象添加多个监听。当被监听的事件发生时,可以通过read()函数从inotify对象中将事件信息读取出来。Inotify对象可以通过inotify_init创建。

  • watch对象则用来描述文件系统的变化事件的监听。它是一个二元组,包括监听目标和事件掩码两个元素。监听目标是文件系统的一个路径,可以是文件也可以是文件夹。而事件掩码则表示了需要需要监听的事件类型,掩码中的每一位代表一种事件。可以监听的事件种类很多,其中就包括文件的创建(IN_CREATE)与删除(IN_DELETE)。以下代码即可将一个用于监听输入设备节点的创建与删除的watch对象添加到inotify对象中:

int wd = inotify_add_watch (inotifyFd, “/dev/input”,IN_CREATE | IN_DELETE);

完成上述watch对象的添加后,当/dev/input/下的设备节点发生创建与删除操作时,都会将相应的事件信息写入到inotifyFd所描述的inotify对象中,此时可以通过read()函数从inotifyFd描述符中将事件信息读取出来。

事件信息使用结构体inotify_event进行描述:

struct inotify_event {
       __s32           wd;             /* 事件对应的Watch对象的描述符 */
       __u32           mask;           /* 事件类型,例如文件被删除,此处值为IN_DELETE */
       __u32           cookie;
       __u32           len;            /* name字段的长度 */
       char            name[0];        /* 可变长的字段,用于存储产生此事件的文件路径*/
};

当有监听事件发生时,可以通过如下方式将一个或多个未读取的事件信息读取出来:

size_t len = read (inotifyFd, events_buf,BUF_LEN);

其中events_buf是inotify_event的数组指针,能够读取的事件数量由取决于数组的长度。成功读取事件信息后,便可根据inotify_event结构体的字段判断事件类型以及产生事件的文件路径了。

INotify机制的使用过程

  • 通过inotify_init()创建一个inotify对象。

  • 通过inotify_add_watch将一个或多个监听添加到inotify对象中。

  • 通过read()函数从inotify对象中读取监听事件。当没有新事件发生时,inotify对象中无任何可读数据。

通过INotify机制避免了轮询文件系统的麻烦,但是还有一个问题,INotify机制并不是通过回调的方式通知事件,而需要使用者主动从INotify对象中进行事件读取,这里借助Linux的Epoll机制。

Epoll

Epoll可以使用一次等待监听多个描述符的可读/可写状态。等待返回时携带了可读的描述符或自定义的数据,使用者可以据此读取所需的数据后可以再次进入等待。因此不需要为每个描述符创建独立的线程进行阻塞读取,避免了资源浪费的同时又可以获得较快的响应速度。

epoll_create(int max_fds):创建一个epoll对象的描述符,之后对epoll的操作均使用这个描述符完成。max_fds参数表示了此epoll对象可以监听的描述符的最大数量。

int epoll_create1(int flag);

当flag是0时,表示和epoll_create函数完全一样,不需要size的提示了

当flag = EPOLL_CLOEXEC,创建的epfd会设置FD_CLOEXEC

当flag = EPOLL_NONBLOCK,创建的epfd会设置为非阻塞

FD_CLOEXEC是fd的一个标识说明,用来设置文件close-on-exec状态的。当close-on-exec状态为0时,调用exec时,fd不会被关闭;状态非零时则会被关闭,这样做可以防止fd泄露给执行exec后的进程

epoll_ctl (int epfd, int op,int fd, struct epoll_event *event):用于管理注册事件的函数。这个函数可以增加/删除/修改事件的注册。

op表示了何种操作,包括EPOLL_CTL_ADD/DEL/MOD三种,分别表示增加/删除/修改注册事件。

int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout):用于等待事件的到来。当此函数返回时,events数组参数中将会包含产生事件的文件描述符。函数返回值表示获取了多少个事件。

Epoll机制的使用过程

创建epoll对象:

Int epfd = epoll_create(MAX_FDS)

填充epoll_event结构体,以描述监控事件。

通过epoll_ctl()函数将此描述符与epoll_event结构体注册进epoll对象,重复这个步骤可以将多个文件描述符的多种事件监听注册到epoll对象中。

使用epoll_wait()函数等待事件,会使调用者陷入等待状态,直到其注册的事件之一发生之后才会返回,并且携带了刚刚发生的事件的详细信息,在如getEvents中调用。

EventHub的创建过程
  • 创建mEpollFd用于监听是否有数据(有无事件)可读。
  • 创建mINotifyFd将它注册到DEVICE_PATH(这里路径就是/dev/input)节点,并将它交给内核用于监听该设备节点的增删数据事件。那么只要有数据增删的事件到来,epoll_wait()就会返回,使得EventHub能收到来自系统的通知,并获取事件的详细信息。
  • 调用epoll_ctl函数将mEpollFd和mINotifyFd注册到epoll中。
  • 定义int wakeFd[2]作为事件传输管道的读写两端,并将读端注册到epoll中让mEpollFd监听。

EventHub 是 Android 系统中负责处理输入事件的类,它位于 frameworks/native/services/inputflinger 目录下。这个类的主要职责是监听和管理 /dev/input 目录下的所有输入设备,并将这些设备的事件转换为 Android 系统可以理解的事件格式。

getEvents 方法中,它首先检查是否需要扫描设备,如果需要,它会调用 scanDevicesLocked 方法来遍历 /dev/input 目录下的所有设备,打开这些设备并存储到 Device 结构体中,同时将这些设备的文件描述符的可读事件注册到 Epoll 中。当设备的输入事件到来时,Epoll 会在 getEvents 函数的调用中产生一条 epoll 事件。

然后,它遍历 mClosingDevices 链表,为每一个已卸载的设备生成 DEVICE_REMOVED 事件。

接着,它通过 Epoll 事件的 data 字段确定此事件表示了 mINotifyFd 可读,即 Epoll 事件的处理。如果 mPendingINotify 事件待处理,它会调用 readNotifyLocked 函数来读取并处理存储在 mINotifyFd 中的 INotif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值