输入系统的初始化

2016-2-25 chenhui 输入系统

每一个设备驱动都会在用户空间提供一个文件作为访问接口,输入子系统的文件存放在 /dev/input/ 目录下面,文件名以 event<Number> 格式生成,用户空间可以对他调用 ioctl() 来得到他是什么样的设备以及他的相关信息。


Android 提供了 getevent 工具来监听所有输入设备的输入,我们可以使用 adb shell 进入手机的命令行,然后执行 getevent 命令,然后我们所进行的所有触屏、按键操作都会被他捕获,并打印出相关信息,比如下面这段信息就是 getevent 捕获到我的手机的触屏事件和按键事件后打印出的信息。

/dev/input/event3: 0000 0000 00000000
/dev/input/event3: 0001 014a 00000000
/dev/input/event3: 0000 0002 00000000
/dev/input/event3: 0000 0000 00000000
/dev/input/event0: 0001 0072 00000001
/dev/input/event0: 0000 0000 00000000
/dev/input/event0: 0001 0072 00000000
/dev/input/event0: 0000 0000 00000000

上面是 getevent 捕获到的输入信息,event3 是触屏驱动的接口,event0 则是按键。触屏信息会被封装成 MotionEvent 对象,按键信息则会被封装成 KeyEvent 对象。

上面这段输入信息共分为三个部分,这三个部分每一个部分都是一个十六进制的数字:

  • 部分一,事件类型。01 表示按下,00 表示抬起。
  • 部分二,事件代码。对于按键来说,他代表了哪个键被按下。
  • 部分三,事件值。对于按键来说,按下后触发两次,分别是 1 和 0;松开后也触发两次,都是 0.

除了 getevent 之外,Android 还提供了 sendevent,他可以往一个输入设备写入事件,以模拟用户输入,格式为:sendevent <路径> <类型> <代码> <值>。


当然,getevent 只是提供给我们的一个工具,Android 本身不是通过他来获得输入信息的,而是通过 EventHub 得到的。EventHub 和 getevent 一样,都是通过 epoll 来监听 /dev/input/ 目录下所有设备文件的。


这个 EventHub 是怎么来的?又起到什么作用呢?这我们就得从输入系统服务 InputManagerServer 的创建开始说起。


先来一个流程:

  1. Java:创建 WindowManagerService 
  2. Java:WindowManagerService 创建 InputManager 对象
  3. Native:创建 NativeInputManager 对象
  4. Native:创建 EventHub 对象并监听 /dev/input 目录所有文件
  5. Native:创建 InputManager 对象
  6. Native:InputManager 创建 InputReader和 InputDispatcher 对象
  7. Native:为 InputReader和 InputDispatcher 创建独立的线程
  8. Java:调用 InputManager 对象的 start 方法来启动 InputReader和 InputDispatcher
  9. Native:InputReader和 InputDispatcher 线程开始运行



在 WindowManagerService 刚创建的时候,就创建了 InputManagerServer


    private WindowManagerService(Context context, PowerManagerService pm,
            boolean haveInputMethods) {
	

		...
			
        mInputManager = new InputManager(context, this);

        ...

        mInputManager.start();

	...
    }


这里涉及到两个方法,一个是 InputManager 的构造方法,另一个则是 InputManager 的 start() 方法。


    public InputManager(Context context, 
			WindowManagerService windowManagerService) {
        this.mContext = context;
        this.mWindowManagerService = windowManagerService;
        
        this.mCallbacks = new Callbacks();
        
        init();
    }
    
    private void init() {
        Slog.i(TAG, "Initializing input manager");
        nativeInit(mCallbacks);
    }
    
    public void start() {
        Slog.i(TAG, "Starting input manager");
        nativeStart();
    }


构造函数和 start() 方法分别调用了两个本地方法:nativeInit() 创建了其中 EventHub 和两个非常重要的对象;nativeStart() 则启动了两个线程。这两个对象和两个线程非常重要,想知道是怎么回事?请继续往下看。

nativeInit() 对应的方法为 android_server_InputManager_nativeInit():


static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
        jobject callbacks) {
    if (gNativeInputManager == NULL) {
        gNativeInputManager = new NativeInputManager(callbacks);
    } else {
        LOGE("Input manager already initialized.");
        jniThrowRuntimeException(env, "Input manager already initialized.");
    }
}


android_server_InputManager_nativeInit() 创建了一个 NativeInputManager 对象,重点来了!

下面是 NativeInputManager 类的构造方法:


NativeInputManager::NativeInputManager(jobject callbacksObj) :
    mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1),
    mMaxEventsPerSecond(-1),
    mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0) {
    JNIEnv* env = jniEnv();

    mCallbacksObj = env->NewGlobalRef(callbacksObj);

    // 创建 EventHub
    // sp<EventHub> 是一个指针指针
    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}


EventHub 就是在这里创建的!

下面是 EventHub 的构造方法:


EventHub::EventHub(void) :
        mBuiltInKeyboardId(-1), mNextDeviceId(1),
        mOpeningDevices(0), mClosingDevices(0),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false), mNeedToScanDevices(true),
        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);

    mNumCpus = sysconf(_SC_NPROCESSORS_ONLN);
	
    // 创建 epoll 文件
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);
	
    // 创建 inotify 文件,监听 "/dev/input" 里的文件改动
    mINotifyFd = inotify_init();
    int result = inotify_add_watch(mINotifyFd, "/dev/input", IN_DELETE | IN_CREATE);
    LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s.  errno=%d",
            DEVICE_PATH, errno);

    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    // 监听 inotify 文件,这样当文件改动时,我就能知道了
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    ....
}



唉!这个函数实在是有点蛋疼,说实话 C++ 的初始化列表这功能实在是太装逼了,搞得人很纠结。

不管怎么样,在构造函数执行完之后,EventHub 就成功开启了 epoll 机制,并监听了 inotify 文件和一个管道,这个 inotify 文件好理解,就是当 /dev/input/ 目录有文件变动时,他就会可读,然后可以读出变动的文件的信息。

这里还有一点很重要,那就是这里并没有监听 /dev/input/ 目录下所有的设备文件!那么怎么办!当然有办法,设备文件的扫描监听不是在这里完成的,而是在 InputReader 向 EventHub 获取输入事件时扫描的。这里先不讲,下次讲到输入事件的获取时会说道。


注意,EventHub 本身并不会去读取输入信息,他只是提供了读取输入信息的能力,而是否读取是由另一个对象控制的。这个对象就是 InputReader!

从名字中就可以看出来,InputReader 就是专门用来读取输入信息的。InputReader 在 new EventHub 后的 new InputManager 的构造函数中被创建:


InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}


可以看出,他上面除了创建一个 InputReader 对象之外,还创建了一个 InputDispatcher 对象。

InputReader 在得到输入信息后,会把输入信息交给 InputDispatcher 对象,然后 InputDispatcher 对象再从他保管的所有 Window 中找到一个合适的,把这个事件交给他处理,Window 得到事件后,他的 ViewRootImpl 对象会沿着 View 树分发件。

InputReader 和 InputDispatcher 这两个对象都是分别运行在一个线程中的,这个线程就在 initialize() 函数中被创建:


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


这样,InputReader 和 InputDispatcher 的线程就成功创建了,但是还没有开始运行!那么,他什么时候才开始运行呢?在文章开头的 Java 层的 InputManager 对象的创建段中可以看出,InputManager 对象在创建成功后,就立刻调用了他的 start() 方法,这个 start() 方法又是一个本地方法,他对应的方法为:android_server_InputManager_nativeStart()


static void android_server_InputManager_nativeStart(JNIEnv* env, jclass clazz) {
    if (checkInputManagerUnitialized(env)) {
        return;
    }

    status_t result = gNativeInputManager->getInputManager()->start();
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }
}


继续看 start() 这个方法:


status_t InputManager::start() { 
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    if (result) {
        LOGE("Could not start InputDispatcher thread due to error %d.", result);
        return result;
    }

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    if (result) {
        LOGE("Could not start InputReader thread due to error %d.", result);

        mDispatcherThread->requestExit();
        return result;
    }

    return OK;
}


就是在这里,InputReader 和 InputDispatcher 的线程就开始运行了!或者说,Android 底层开始读取输入设备上报的

至此,Android 的输入系统的初始化就完成了。




发表评论:

Copyright ©2015-2016 freehui All rights reserved