V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Cheelok
V2EX  ›  Android

Android Input 子系统: Input 进程的创建,监听线程的启动

  •  
  •   Cheelok · 2017-09-19 22:21:31 +08:00 · 8573 次点击
    这是一个创建于 2622 天前的主题,其中的信息可能已经有所发展或是发生改变。

    专栏地址: https://zhuanlan.zhihu.com/p/29152319

    从我个人的理解来看,Android 的 Input 系统其实就是系统级的事件处理、分发框架,它需要的功能模块大致有:事件读取、事件分类、事件分发。那么我们就从整个 Input 系统的输入源入手,了解事件是如何被输入到 Input 系统中的。

    在看代码前我们先想一想,如果要我们设计一个事件分发框架的输入读取模块,要考虑到哪些子模块:

    1. 事件生成模块(当用户对设备进行操作产生 InputEvent,硬件产生中断将事件交给驱动,驱动交给内核,内核交给 framework )
    2. 事件监听模块(这里就很像设计一个服务器,为了及时响应来自客户端的请求,则需要启动一个线程监听)
    3. 事件读取模块
    4. 事件分发模块

    那么现在我们最起码可以知道整个学习的起点了,就是 Input 系统中,负责监听的线程是谁,监听的过程中它们做了什么。在开始之前,给大家分享一张我根据本文内容画的图:

    InputManagerService 初始化概览

    首先,有几点共识我们都可以达成:

    1. Android Framework 层的 Service ( Java )都是由 system_server 进程创建的(由于没有 fork,因此都运行在 system_server 进程中)
    2. Service 创建后就会交给运行在 system_server 进程中的 ServiceManager 管理。

    因此对于 InputManagerService 的创建,我们可以在 SystemServer 的 startOtherServices()方法中找到,该方法做了以下事情:

    1. 创建 InputManagerService 对象
    2. 将它交给 ServiceManager 管理
    3. 将 WindowManagerService 的 InputMonitor 注册到 InputManagerService 中作为窗口响应事件后的回调
    4. 完成以上工作后启动 InputManagerService。
    SystemServer.java
    
    startOtherServices(){
        ……
        inputManager = new InputManagerService(context);
        ……
        inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
        inputManager.start();
        ……
    }
    

    接下来我们就逐部分学习相应的处理。

    InputManagerService 对象的创建

    创建 InputManagerService 对象时会完成以下工作:

    1. 创建一个负责处理 DisplayThread 线程中的 Message 的 Handler
    2. 调用 nativeInit 初始化 native 层的 InputManagerService,初始化的时候传入了 DisplayThread 的消息队列
    3. 用 mPtr 保存 native 层的 InputManagerService
    4. 初始化完成后将 Service 添加到 LocalServices,通过 Map 以键值对的形式存储
    InputManagerService.java
    
    public InputManagerService(Context context) {
        this.mContext = context;
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
    
        mUseDevInputEventForAudioJack =
                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
        Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
                + mUseDevInputEventForAudioJack);
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
    
        LocalServices.addService(InputManagerInternal.class, new LocalService());
    }
    

    这里可能有人就会问了,为什么 InputManagerService 要和 DisplayThread 绑定在一起?大家不妨想想,InputEvent 无论如何被获取、归类、分发,最终还是要被处理,也就意味着最终它的处理结果都要在 UI 上体现,那么 InputManagerService 自然要选择和 UI 亲近一些的线程在一起了。

    但是问题又来了,应用都是运行在自己的主线程里的,难道 InputManagerService 要一个个绑定么,还是一个个轮询?这些做法都太过低效,那换个办法,可不可以和某个管理或非常亲近所有应用 UI 的线程绑定在一起呢?

    答案是什么,我在这里先不说,大家可以利用自己的知识想想。

    初始化 native 层的 InputManagerService

    在 nativeInit 函数中,将 Java 层的 MessageQueue 转换为 native 层的 MessageQueue,然后再取出 Looper 用于 NativeInputManager 的初始化。可见这里的重头戏就是 NativeInputManager 的创建,这个过程做了以下事情:

    1. 将 Java 层的 Context 和 InputManagerService 转换为 native 层的 Context 和 InputManagerService 存储在 mContextObj 和 mServiceObj 中
    2. 初始化变量
    3. 创建 EventHub
    4. 创建 InputManager
    com_android_server_input_InputManagerService.cpp
    
    NativeInputManager::NativeInputManager(jobject contextObj,
            jobject serviceObj, const sp<Looper>& looper) :
            mLooper(looper), mInteractive(true) {
        JNIEnv* env = jniEnv();
    
        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;
        }
        mInteractive = true;
    
        sp<EventHub> eventHub = new EventHub();
        mInputManager = new InputManager(eventHub, this, this);
    }
    

    EventHub

    看到这里很多人就会想,EventHub 是什么?取英语释义来看,它的意思是事件枢纽。我们在文章开头的时候也提到过,Input 系统的事件来源于驱动 /内核,那么我们可以猜测 EventHub 是处理来自驱动 /内核的元事件的枢纽。接下来就在源码中验证我们的想法吧。

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

    1. 创建 mEpollFd 用于监听是否有数据(有无事件)可读
    2. 创建 mINotifyFd 将它注册到 DEVICE_PATH (这里路径就是 /dev/input )节点,并将它交给内核用于监听该设备节点的增删数据事件。那么只要有数据增删的事件到来,epoll_wait()就会返回,使得 EventHub 能收到来自系统的通知,并获取事件的详细信息
    3. 调用 epoll_ctl 函数将 mEpollFd 和 mINotifyFd 注册到 epoll 中
    4. 定义 int wakeFd[2]作为事件传输管道的读写两端,并将读端注册到 epoll 中让 mEpollFd 监听
    EventHub.cpp
    
    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_create(EPOLL_SIZE_HINT);
        LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);
    
        mINotifyFd = inotify_init();
        int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
        ……
        result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
        ……
        int wakeFds[2];
        result = pipe(wakeFds);
        ……
        mWakeReadPipeFd = wakeFds[0];
        mWakeWritePipeFd = wakeFds[1];
    
        result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
        ……
        result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
        ……
        result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
        ……
    }
    

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

    InputManager 的创建

    接下来继续说 InputManager 的创建,它的创建就简单多了,创建一个 InputDispatcher 对象用于分发事件,一个 InputReader 对象用于读事件并把事件交给 InputDispatcher 分发,,然后调用 initialize()初始化,其实也就是创建了 InputReaderThread 和 InputDispatcherThread。

    InputManager.cpp
    
    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();
    }
    
    void InputManager::initialize() {
        mReaderThread = new InputReaderThread(mReader);
        mDispatcherThread = new InputDispatcherThread(mDispatcher);
    }
    

    InputDispatcher 和 InputReader 的创建都相对简单。InputDispatcher 会创建自己线程的 Looper,以及设置根据传入的 dispatchPolicy 设置分发规则。InputReader 则会将传入的 InputDispatcher 封装为监听对象存起来,做一些数据初始化就结束了。

    至此,InputManagerService 对象的初始化就完成了,根据开头说的,接下来就会调用 InputManagerService 的 start()方法。

    监听线程 InputReader 和 InputDispatcher 的启动

    在 start()方法中,做了以下事情:

    1. 调用 nativeStart 方法,其实就是调用 InputManager 的 start()方法
    2. 将 InputManagerService 交给 WatchDog 监控
    3. 注册触控点速度、显示触控的观察者,并注册广播监控它们
    4. 主动调用 updateXXX 方法更新(初始化)
    InputManagerService.java
    
    public void start() {
        Slog.i(TAG, "Starting input manager");
        nativeStart(mPtr);
    
        // Add ourself to the Watchdog monitors.
        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);
    
        updatePointerSpeedFromSettings();
        updateShowTouchesFromSettings();
        updateAccessibilityLargePointerFromSettings();
    }
    

    显而易见这里最值得关注的就是 InputManager 的 start()方法了,可惜这个方法并不值得我们如此关心,因为它做的事情很简单,就是启动 InputDispatcherThread 和 InputReaderThread 开始监听。

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

    那么 InputReaderThread 线程是怎么和 EventHub 关联起来的呢?

    对于 InputReadThread:

    1. 启动后循环执行 mReader->loopOnce()
    2. loopOnce()中会调用 mEventHub->getEvents 读取事件
    3. 读到了事件就会调用 processEventsLocked 处理事件
    4. 处理完成后调用 getInputDevicesLocked 获取输入设备信息
    5. 调用 mPolicy->notifyInputDevicesChanged 函数利用 InputManagerService 的代理通过 Handler 发送 MSG_DELIVER_INPUT_DEVICES_CHANGED 消息,通知输入设备发生了变化
    6. 最后调用 mQueuedListener->flush(),将事件队列中的所有事件交给在 InputReader 中注册过的 InputDispatcher
    bool InputReaderThread::threadLoop() {
        mReader->loopOnce();
        return true;
    }
    
    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();
    }
    

    至此,Input 进程的创建,监听线程的启动相关学习就结束了。

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   951 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 22:54 · PVG 06:54 · LAX 14:54 · JFK 17:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.