天天看点

Input输入系统(一)输入事件系统框架Input系统启动流程InputReader

输入事件系统框架

Android事件输入系统主要负责键盘、触屏、鼠标等输入设备的事件输入及向焦点窗口和焦点视图的事件派发,插入,过滤,拦截等功能。Android支持的输入设备主要有:键盘、鼠标、触摸屏、轨迹球、游戏摇杆/手柄、绘图板等。

Android系统中输入系统主要包括如下几部分:View,InputDispatcher,InputReader,EventHub ,kernel几部分,他们之间的关系如下:

Input输入系统(一)输入事件系统框架Input系统启动流程InputReader

与其他模块的交互关系如下:

Input输入系统(一)输入事件系统框架Input系统启动流程InputReader

Input系统启动流程

Input在SystemServer中的startOtherServices方法中启动,在启动过程中主要过程如下:

Input输入系统(一)输入事件系统框架Input系统启动流程InputReader

主要代码如下:

    startOtherServices(){
            inputManager = new InputManagerService(context);
            inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
            inputManager.start();
      ......
      }
           

创建InputMangerService对象

 public InputManagerService(Context context) {
        this.mContext = context;
        //创建一个负责处理DisplayThread线程中的Message的Handler
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
​
        mUseDevInputEventForAudioJack =
                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
        Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
                + mUseDevInputEventForAudioJack);
        //用mPtr保存native层的InputManagerService
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
​
        String doubleTouchGestureEnablePath = context.getResources().getString(
                R.string.config_doubleTouchGestureEnableFile);
        mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
            new File(doubleTouchGestureEnablePath);
        //初始化完成后将Service添加到LocalServices,通过Map以键值对的形式存储
        LocalServices.addService(InputManagerInternal.class, new LocalService());
    }
           

初始化native层的InputManagerService

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();
    //将Java层的Context和InputManagerService转换为native层的Context和InputManagerService存储在mContextObj和mServiceObj中
    mContextObj = env->NewGlobalRef(contextObj);
    mServiceObj = env->NewGlobalRef(serviceObj);
    //初始化变量
    {
        AutoMutex _l(mLock);
        mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
        mLocked.pointerSpeed = 0;
        mLocked.pointerGesturesEnabled = true;
        mLocked.showTouches = false;
        mLocked.pointerCapture = false;
    }
    mInteractive = true;
   //创建EventHub
    sp<EventHub> eventHub = new EventHub();
    //创建InputManager
    mInputManager = new InputManager(eventHub, this, this);
}
           

EventHub的创建(见2.1)

InputManager的创建

//创建一个InputDispatcher对象用于分发事件,一个InputReader对象用于读事件并把事件交给InputDispatcher分发,然后调用initialize()初始化,其实也就是创建了InputReaderThread和InputDispatcherThread
InputManager::InputManager(
        const sp<InputReaderInterface>& reader,
        const sp<InputDispatcherInterface>& dispatcher) :
        mReader(reader),
        mDispatcher(dispatcher) {
    initialize();
}
//InputDispatcher和InputReader的创建都相对简单。InputDispatcher会创建自己线程的Looper,以及设置根据传入的dispatchPolicy设置分发规则。InputReader则会将传入的InputDispatcher封装为监听对象存起来,做一些数据初始化就结束了
​
void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
           

InputManagerService的start()

   public void start() {
        Slog.i(TAG, "Starting input manager");
        //调用nativeStart方法,其实就是调用InputManager的start()方法
        nativeStart(mPtr);
​
        // 将InputManagerService交给WatchDog监控
        Watchdog.getInstance().addMonitor(this);
        //注册触控点速度、显示触控的观察者,并注册广播监控它们
        registerPointerSpeedSettingObserver();
        registerShowTouchesSettingObserver();
        registerAccessibilityLargePointerSettingObserver();
​
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                updatePointerSpeedFromSettings();
                updateShowTouchesFromSettings();
                updateAccessibilityLargePointerFromSettings();
            }
        }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
        //主动调用updateXXX方法更新(初始化)
        updatePointerSpeedFromSettings();
        updateShowTouchesFromSettings();
        updateAccessibilityLargePointerFromSettings();
    
           

Input涉及主要线程

从上述结构图中看到Input系统涉及到的主要线程为:

InputReader线程:通过EventHub从/dev/input节点获取事件,转换成EventEntry事件加入到InputDispatcher的mInboundQueue。当IMS.start启动时启动

InputDispatcher线程:从mInboundQueue队列取出事件,转换成DispatchEntry事件加入到connection的outboundQueue队列。再然后开始处理分发事件,取出outbound队列,放入waitQueue.

UI线程:(DisplayThread)创建socket pair,分别位于InputDispatcher线程和focused窗口所在进程的UI主线程,可相互通信。

a:UI主线程:通setFdEvents() 监听socket客户端,收到消息后回调NativeInputEventReceiver();

b:InputDispatcher线程: 通过IMS.registerInputChannel(),监听socket服务端,收到消息后回调handleReceiveCallback

InputReader

InputReader同样是system_server的子线程,负责输入设备的管理和输入事件的处理,主要的作用如下:

  1. 从EventHub中获得底层事件
  2. 处理输入设备添加、删除事件,管理设备的InputMapper
  3. 处理输入事件,由设备对应的各个InputMapper处理之后交付InputDispatcher进行分发
Input输入系统(一)输入事件系统框架Input系统启动流程InputReader

从上述结构图看到:

InputReaderThread线程是和EventHub关联是通过InputReaderThread线程启动后,会循环执行mReader->looperonce,loopOnce()中会调用mEventHub->getEvents读取事件,读取事件后读到了事件就会调用processEventsLocked处理事件处理完成后调用getInputDevicesLocked获取输入设备信息调用mPolicy->notifyInputDevicesChanged函数利用InputManagerService的代理通过Handler发送MSG_DELIVER_INPUT_DEVICES_CHANGED消息,通知输入设备发生了变化最后调用mQueuedListener->flush(),将事件队列中的所有事件交给在InputReader中注册过的InputDispatcher

EventHub

EventHub为输入系统的最底层模块,输入事件的源头,此类主要的作用为:

►监控输入设备添加、删除事件

►获取新增设备的信息,如设备的类别、支持哪些按键、键盘映射表等。

►获取input_event,封装成RawEvent传递给InputReader

►支持查询输入设备当前的状态。

●组合键状态

●是否有LED灯或支持振动

new EventHub

EventHub的创建过程中做了以下事情:

创建mEpollFd用于监听是否有数据(有无事件)可读

创建mINotifyFd将它注册到DEVICE_PATH(这里路径就是/dev/input)节点,并将它交给内核用于监听该设备节点的增删数据事件。那么只要有数据增删的事件到来,epoll_wait()就会返回,使得EventHub能收到来自系统的通知,并获取事件的详细信息

调用epoll_ctl函数将mEpollFd和mINotifyFd注册到epoll中

定义int wakeFd[2]作为事件传输管道的读写两端,并将读端注册到epoll中让mEpollFd监听

相关代码如下:

EventHub::EventHub(void) :
        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
        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);
    //创建mEpollFd用于监听是否有数据(有无事件)可读,EPOLL_SIZE_HINT:8
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
  
   //创建mINotifyFd将它注册到DEVICE_PATH(这里路径就是/dev/input)节点,并将它交给内核用于监听该设备节点的增删数据事件。那么只要有数据增删的事件到来,epoll_wait()就会返回,使得EventHub能收到来自系统的通知,并获取事件的详细信息
    mINotifyFd = inotify_init();
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, 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));//epoll_event这个结构体清空
    eventItem.events = EPOLLIN;//监测的事件类型为EPOLLIN
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    //调用epoll_ctl函数将mEpollFd和mINotifyFd注册到epoll中,监测的fd为:mINotifyFd
    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);
    //作为事件传输管道的读写两端,并将读端注册到epoll中让mEpollFd监听
    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];//建立写入事件的通道
​
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    eventItem.data.u32 = EPOLL_ID_WAKE;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
    int major, minor;
    getLinuxRelease(&major, &minor);
    // EPOLLWAKEUP was introduced in kernel 3.5
    mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);
}
           
Input输入系统(一)输入事件系统框架Input系统启动流程InputReader

那么这里抛出一个问题:为什么要把管道的读端注册到epoll中?假如EventHub因为getEvents读不到事件而阻塞在epoll_wait()里,而我们没有绑定读端的话,我们要怎么唤醒EventHub?如果绑定了管道的读端,我们就可以通过向管道的写端写数据从而让EventHub因为得到管道写端的数据而被唤醒。

EventHub如何监听设备的插拔和读取底层的事件,主要通过inotify和epoll

Input输入系统(一)输入事件系统框架Input系统启动流程InputReader