android 按键事件上报机制

1. 按键上报和分发机制

按键处理设计的整体思路是驱动层会有一个消息队列来存放事件,会有一个Reader来不停的读取事件,一个Dispatcher来分发消息队列中的事件。Dispatcher分发的事件最后会通过jni上报到InputManagerService,然后通过接口最后传递给PhoneWindow,这里再根据不同的按键事件类型来做不同的处理。

1.1 输入事件处理准备工作

当系统开机之后SystemServer会启动InputManagerService,在SystemServer.java中的startOtherServices()方法中启动:

    Slog.i(TAG, "Input Manager");
    inputManager = new InputManagerService(context);

    Slog.i(TAG, "Window Manager");
    wm = WindowManagerService.main(context, inputManager,
            mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
            !mFirstBoot, mOnlyCore);
    ServiceManager.addService(Context.WINDOW_SERVICE, wm);
    ServiceManager.addService(Context.INPUT_SERVICE, inputManager);

    mActivityManagerService.setWindowManager(wm);

    inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
    inputManager.start();
复制代码

看一下InputManagerService的构造方法:

    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());
    }
复制代码

主要是通过JNI的方式调用nativeInit方法传入一个消息队列作为参数,nativeInit对应是com_android_server_input_InputManagerService.cpp中的nativeInit。

    static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
            jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
        sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
        if (messageQueue == NULL) {
            jniThrowRuntimeException(env, "MessageQueue is not initialized.");
            return 0;
        }
    
        NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
                messageQueue->getLooper());
        im->incStrong(0);
        return reinterpret_cast<jlong>(im);
    }
复制代码

这里又new了一个NativeInputManager的对象,构造的参数主要还是这个消息队列的Looper。继续看一下NativeInputManager的构造函数,还是在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);
    }
复制代码

这里主要就是new出了InputManager的实例,最后再看一下构造函数和initialize方法,代码位于frameworks/native/services/inputflinger/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();
    }
    
    InputManager::InputManager(
            const sp<InputReaderInterface>& reader,
            const sp<InputDispatcherInterface>& dispatcher) :
            mReader(reader),
            mDispatcher(dispatcher) {
        initialize();
    }
    
    InputManager::~InputManager() {
        stop();
    }
    
    void InputManager::initialize() {
        mReaderThread = new InputReaderThread(mReader);
        mDispatcherThread = new InputDispatcherThread(mDispatcher);
    }
复制代码

在initialize中初始化了mReader和mDispatcher,以及两个相关的Thread。至此所需的关键对象都已经创建准备好,但是线程并未run起来。

1.2 启动事件读取和分发线程

在SystemServer中启动InputManagerService之后,调用了start方法:

    inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
    inputManager.start();
复制代码

在InputManagerService中的start方法中又调用了nativeStart方法,这个方法也是本地方法,具体实现在com_android_server_input_InputManagerService.cpp中。

static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

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

接下来又调用了InputManager.cpp的start方法:

    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;
    }
复制代码

在这里将DispatcherThread和ReaderThread运行起来。

一张图总结下:

1.3 事件获取和分发

首先由InputReaderThread等待按键消息到来,该thread在threadLoop中无尽的调用InputReader的loopOnce方法,代码位于frameworks/native/services/inputflinger/InputReader.cpp:

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}
复制代码

在loopOnce方法中会通过EventHub来获取事件放入buffer中:

size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
复制代码

frameworks/native/services/inputflinger/EventHub.cpp中的getEvents方法一部分:

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ALOG_ASSERT(bufferSize >= 1);

    AutoMutex _l(mLock);

    struct input_event readBuffer[bufferSize];

    RawEvent* event = buffer;
复制代码

InputReader从设备文件(adb shell getevent就是读的这个文件)中读取的是RawEvent,在交给InputDispatcher进行分发之前,它需要先把RawEvent进行转化分类,拆分成KeyEvent、MotionEvent、TrackEvent各种类型等。如需了解拆分过程可以参照文章 Android输入事件流程中的EventHub分析及源码演示 来深入理解EventHub.cpp中的拆分过程。

在getEvents方法的最后来将队列中事件刷给监听器,监听器实际上就是InputDispatcher事件分发器。

 // Flush queued events out to the listener. This must happen outside of the lock because the listener could potentially call back into the InputReader's methods, such as getScanCodeState, or become blocked on another thread similarly waiting to acquire the InputReader lock thereby resulting in a deadlock. This situation is actually quite plausible because the listener is actually the input dispatcher, which calls into the window manager, which occasionally calls into the input reader.     
 mQueuedListener->flush();
复制代码

然后会调用到frameworks/native/services/inputflinger/InputDispatcher.cpp的notifyKey方法。

KeyEvent event;
event.initialize(args->deviceId, args->source, args->action,
        flags, keyCode, args->scanCode, metaState, 0,
        args->downTime, args->eventTime);

mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
复制代码

在notifyKey方法中通过InputDispatcherPolicyInterface接口来调用到NativeInputManager的interceptKeyBeforeQueueing方法通知是否需要在入队前对事件进行处理,然后接着调用InputDispatcher的enqueueInboundEventLocked方法将事件放入到队尾中。

    KeyEntry* newEntry = new KeyEntry(args->eventTime,
            args->deviceId, args->source, policyFlags,
            args->action, flags, keyCode, args->scanCode,
            metaState, repeatCount, args->downTime);

    needWake = enqueueInboundEventLocked(newEntry);
复制代码
    bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
        bool needWake = mInboundQueue.isEmpty();
        mInboundQueue.enqueueAtTail(entry);
        traceInboundQueueLengthLocked();
    
        switch (entry->type) {
复制代码

以上流程是InputReader获取到设备事件通知分发器并存放到事件队列中。

下面将介绍InputDispatcher如何从事件队列中读取事件并分发出去。首先在InputDispatcherThread的threadLoop中无尽的调用dispatchOnce方法,该方法两个功能:1、调用dispatchOnceInnerLocked分发事件;2、调用runCommandsLockedInterruptible来处理CommandQueue中的命令,出队并处理,直到队列为空。

下面具体介绍事件的分发,如果当前没有挂起的命令即CommandQueue为空,则调用dispatchOnceInnerLocked方法来分发事件,这里也是android系统出现事件响应ANR的地方,在之前挂起的事件命令过多时就会导致新的事件无法分发导致ANR,在dispatchOnceInnerLocked中成功分发后会去调用resetANRTimeoutsLocked()来重置ANR的时间。

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock             AutoMutex _l(mLock);
        mDispatcherIsAliveCondition.broadcast();

        // Run a dispatch loop if there are no pending commands.             // The dispatch loop might enqueue commands to run afterwards.             if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        // Run all pending commands if there are any.             // If any commands were run then force the next poll to wake up immediately.             if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
    } // release lock     
    // Wait for callback or timeout or wake. (make sure we round up, not down)         nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}
复制代码

在dispatchOnceInnerLocked中会处理多种类型的事件,这里关注按键类型的(其他如触摸,设备重置等事件流程稍有区别)。如果Event类型为KEY,最后调用dispatchKeyLocked,再经过doInterceptKeyBeforeDispatchingLockedInterruptible这个方法之后就调用到了InputDispatcherPolicyInterface接口的interceptKeyBeforeDispatching方法。到这里就又很熟悉了,由NativeInputManager实现这个接口,然后又通过jni的方式调用到PhoneWindowManager的interceptKeyBeforeDispatching将按键事件分发传递给了java层。

case EventEntry::TYPE_KEY: {// 如果事件为按键类型         KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
    if (isAppSwitchDue) {
        if (isAppSwitchKeyEventLocked(typedEntry)) {
            resetPendingAppSwitchLocked(true);
            isAppSwitchDue = false;
        } else if (dropReason == DROP_REASON_NOT_DROPPED) {
            dropReason = DROP_REASON_APP_SWITCH;
        }
    }
    if (dropReason == DROP_REASON_NOT_DROPPED
            && isStaleEventLocked(currentTime, typedEntry)) {
        dropReason = DROP_REASON_STALE;
    }
    if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
        dropReason = DROP_REASON_BLOCKED;
    }
    done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
    break;
}
复制代码

事件的处理包括了两条主线:

  • InputReader从EventHub中获取到按键事件,并通知InputDispatcher;InputDispatcher接到通知后调用interceptKeyBeforeQueueing方法进行相关的操作,并把按键事件加入到队列中,等待后面处理;
  • InputDispatcher从消息队列中获取按键消息,调用interceptKeyBeforeDispatching方法判断是否对此消息进行拦截。

小结

这一部分介绍了事件上报和处理机制,首先是准备工作,SystemServer启动InputManagerService,然后依次创建了NativeInputManager、InputManager、InputReader、InputDispatcher这几个关键的对象以及InputReaderThread和InputDispatcherThread这两个关键线程。然后让这个两个thread启动起来,在InputReaderThread无限循环运行时,通过InputReader从EventHub中不断读取events然后通知InputDispatcher将事件入队。而InputDispatcherThread则通过InputDispatcher不停的将队列中的事件分发出去,这就是整个input系统的基本机制。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值