先讲一下基本一般的输入处理方式的知识。一般的输入输出采用生产者,消费者模式,并构造队列进行处理,如下图
这种输入模型在android的系统中很多地方采用,先从最底层说起:
为了由于触屏事件频率很高,android设计者讲一个循环线程,拆分为两级循环,并做了个队列来进行缓冲。
InputDispatcherThread和InputReaderThread
InputDispatcherThread在自己的循环中对InputReaderThread请求同步,InputReaderThread收到同步信号后,把事件放入InputDispatcher的队列中。
具体代码如下:
InputReader.cpp中有很多InputMapper,有SwitchInputMapper,KeyBoardInputMapper,TrackballInputMapper,SingleTouchInputMapper,
MultiTouchInputMapper。当线程从EventHub读取到Event后,调用这些InputMapper的pocess方法:
process如下
- consumeEvent(rawEvent);
方法是关键,下面继续跟进;
device->process(rawEvent)行, 跟进去:
下面进入了IputMapper,InputMapper是个纯虚类,process是个纯虚方法,随便找个例子跟进去:
最关键的是
- sync(rawEvent->when);
展开如下:
这两行,一个是虚拟键盘,一个是触摸屏。
TouchResult touchResult = consumeOffScreenTouches(when, policyFlags);
- dispatchTouches
-
- void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
- // Dispatch pointer down events using the new pointer locations.
- while (!downIdBits.isEmpty()) {
- dispatchTouch(when, policyFlags, &mCurrentTouch,
- activeIdBits, downId, pointerCount, motionEventAction);
- }
- }
- }
- dispatchTouch(when, policyFlags, &mCurrentTouch,
- activeIdBits, downId, pointerCount, motionEventAction);
这个方法展开如下:
- void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags,
- TouchData* touch, BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
- int32_t motionEventAction) {
- int32_t pointerIds[MAX_POINTERS];
- PointerCoords pointerCoords[MAX_POINTERS];
- int32_t motionEventEdgeFlags = 0;
- float xPrecision, yPrecision;
- {
- getDispatcher()->notifyMotion(when, getDeviceId(), getSources(), policyFlags,
- motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
- pointerCount, pointerIds, pointerCoords,
- xPrecision, yPrecision, mDownTime);
- }
这样就到了InputDiaptcher的notifyMotion方法,这个方法很长,都再处理MOVE事件,将无用的删除后,留下如下关键代码:
- void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
- uint32_t policyFlags, int32_t action, int32_t flags, int32_t metaState, int32_t edgeFlags,
- uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
- float xPrecision, float yPrecision, nsecs_t downTime) {
- // Just enqueue a new motion event.
- MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,
- deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
- xPrecision, yPrecision, downTime,
- pointerCount, pointerIds, pointerCoords);
- needWake = enqueueInboundEventLocked(newEntry);
- }
最后一句:
- needWake = enqueueInboundEventLocked(newEntry);
- bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
- bool needWake = mInboundQueue.isEmpty();
- mInboundQueue.enqueueAtTail(entry);
- switch (entry->type) {
- case EventEntry::TYPE_KEY: {
- KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
- if (isAppSwitchKeyEventLocked(keyEntry)) {
- if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
- mAppSwitchSawKeyDown = true;
- } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
- if (mAppSwitchSawKeyDown) {
- #if DEBUG_APP_SWITCH
- LOGD("App switch is pending!");
- #endif
- mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
- mAppSwitchSawKeyDown = false;
- needWake = true;
- }
- }
- }
- break;
- }
- }
- return needWake;
- }
- mInboundQueue正是上面所说的队列。到此为止,从InputReader插入到队列就完成了。
那么InputDispatcher又是如何从队列中取出来的呢?累了。
InputDiapather的
- dispatchOnce
方法如下:
- void InputDispatcher::dispatchOnce() {
- nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
- nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();
- nsecs_t nextWakeupTime = LONG_LONG_MAX;
- { // acquire lock
- AutoMutex _l(mLock);
- dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);
- if (runCommandsLockedInterruptible()) {
- nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
- }
- } // release lock
- // Wait for callback or timeout or wake. (make sure we round up, not down)
- nsecs_t currentTime = now();
- int32_t timeoutMillis;
- if (nextWakeupTime > currentTime) {
- uint64_t timeout = uint64_t(nextWakeupTime - currentTime);
- timeout = (timeout + 999999LL) / 1000000LL;
- timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout);
- } else {
- timeoutMillis = 0;
- }
- mLooper->pollOnce(timeoutMillis);
- }
最关键的是
- dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);
和
- mLooper->pollOnce(timeoutMillis);(这个方法在有个回调,与事件有关的是【管道设备的文件描述符】。
- 具体机制在后面讲。这个东西实际上是个跨进程的信号。发送给了Android的用户进程的JNI,又通过JNI调用InputQueue的的
- 静态方法如:dispatchMotionEvent。后面回提到dispatchMotionEvent才是Android用户进程的事件入口函数。)
- )
代码又长又臭
- void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,
- nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) {
- case EventEntry::TYPE_MOTION: {
- MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
- if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
- dropReason = DROP_REASON_APP_SWITCH;
- }
- done = dispatchMotionLocked(currentTime, typedEntry,
- &dropReason, nextWakeupTime);
- break;
- }
- }
- dispatchMotionLocked
方法调用prepareDispatchCycleLocked,调用startDispatchCycleLocked,最终调用
// Publish the key event.
status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,
action, flags, keyEntry->keyCode, keyEntry->scanCode,
keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
keyEntry->eventTime);或者 // Publish the motion event and the first motion sample.
status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState,
xOffset, yOffset,
motionEntry->xPrecision, motionEntry->yPrecision,
motionEntry->downTime, firstMotionSample->eventTime,
motionEntry->pointerCount, motionEntry->pointerIds,
firstMotionSample->pointerCoords);至此,InputDisapatcher也结束了。
既然发布出去,必然有订阅者:在InputTransport.cpp中
- status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) {
- #if DEBUG_TRANSPORT_ACTIONS
- LOGD("channel '%s' consumer ~ consume",
- mChannel->getName().string());
- #endif
- *outEvent = NULL;
- int ashmemFd = mChannel->getAshmemFd();
- int result = ashmem_pin_region(ashmemFd, 0, 0);
- if (result != ASHMEM_NOT_PURGED) {
- if (result == ASHMEM_WAS_PURGED) {
- LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d because it was purged "
- "which probably indicates that the publisher and consumer are out of sync.",
- mChannel->getName().string(), result, ashmemFd);
- return INVALID_OPERATION;
- }
- LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d.",
- mChannel->getName().string(), result, ashmemFd);
- return UNKNOWN_ERROR;
- }
- if (mSharedMessage->consumed) {
- LOGE("channel '%s' consumer ~ The current message has already been consumed.",
- mChannel->getName().string());
- return INVALID_OPERATION;
- }
- // Acquire but *never release* the semaphore. Contention on the semaphore is used to signal
- // to the publisher that the message has been consumed (or is in the process of being
- // consumed). Eventually the publisher will reinitialize the semaphore for the next message.
- result = sem_wait(& mSharedMessage->semaphore);
- if (result < 0) {
- LOGE("channel '%s' consumer ~ Error %d in sem_wait.",
- mChannel->getName().string(), errno);
- return UNKNOWN_ERROR;
- }
- mSharedMessage->consumed = true;
- switch (mSharedMessage->type) {
- case AINPUT_EVENT_TYPE_KEY: {
- KeyEvent* keyEvent = factory->createKeyEvent();
- if (! keyEvent) return NO_MEMORY;
- populateKeyEvent(keyEvent);
- *outEvent = keyEvent;
- break;
- }
- case AINPUT_EVENT_TYPE_MOTION: {
- MotionEvent* motionEvent = factory->createMotionEvent();
- if (! motionEvent) return NO_MEMORY;
- populateMotionEvent(motionEvent);
- *outEvent = motionEvent;
- break;
- }
- default:
- LOGE("channel '%s' consumer ~ Received message of unknown type %d",
- mChannel->getName().string(), mSharedMessage->type);
- return UNKNOWN_ERROR;
- }
- return OK;
- }
也许我们最关心的是如何订阅的,不得不取看一下JNI的代码,文件android_view_InputQueue.cpp
聚焦到这里
- status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj,
- jobject inputHandlerObj, jobject messageQueueObj) {
- sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
- inputChannelObj);
- if (inputChannel == NULL) {
- LOGW("Input channel is not initialized.");
- return BAD_VALUE;
- }
- #if DEBUG_REGISTRATION
- LOGD("channel '%s' - Registered", inputChannel->getName().string());
- #endif
- sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
- { // acquire lock
- AutoMutex _l(mLock);
- if (getConnectionIndex(inputChannel) >= 0) {
- LOGW("Attempted to register already registered input channel '%s'",
- inputChannel->getName().string());
- return BAD_VALUE;
- }
- uint16_t connectionId = mNextConnectionId++;
- sp<Connection> connection = new Connection(connectionId, inputChannel, looper);
- status_t result = connection->inputConsumer.initialize();
- if (result) {
- LOGW("Failed to initialize input consumer for input channel '%s', status=%d",
- inputChannel->getName().string(), result);
- return result;
- }
- connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj);
- int32_t receiveFd = inputChannel->getReceivePipeFd();
- mConnectionsByReceiveFd.add(receiveFd, connection);
- looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
- } // release lock
- android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
- handleInputChannelDisposed, this);
- return OK;
- }
也许更想知道的是消息队列在什么地方,进入InputQueue.java来看
- public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler,
- MessageQueue messageQueue) {
- if (inputChannel == null) {
- throw new IllegalArgumentException("inputChannel must not be null");
- }
- if (inputHandler == null) {
- throw new IllegalArgumentException("inputHandler must not be null");
- }
- if (messageQueue == null) {
- throw new IllegalArgumentException("messageQueue must not be null");
- }
- synchronized (sLock) {
- if (DEBUG) {
- Slog.d(TAG, "Registering input channel '" + inputChannel + "'");
- }
- nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue);
- }
- }
在ViewRoot.java中有这么几行
- InputQueue.registerInputChannel(mInputChannel, mInputHandler,
- Looper.myQueue());
完毕。
这才牵涉到管道的问题,哪个Java中的Channel对应的正是linux系统的管道。有了管道,才能通过 跨进程方式回调回来,为什么是这个入口,上面进行了解释。具体参照INputQUEUE这个java类的JNI方法
int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data)
这个方法被InputQueue的RegisterInputChannel注册给了系统.系统通过回调,回调的是这个ALOOPER_EVENT_INPUT事件。
looper就是android中的【用户进程的循环】
注册的代码为 :
looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
回调的java函数为
- 回调的java代码的方法入口为:InputQueue.java中的。
- @SuppressWarnings("unused")
- private static void dispatchMotionEvent(InputHandler inputHandler,
- MotionEvent event, long finishedToken) {
- Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
- inputHandler.handleMotion(event, finishedCallback);
- }
这样就回调到了ViewRoot中
只说触摸屏,虚拟键类似,触摸屏调用的是