先講一下基本一般的輸入處理方式的知識。一般的輸入輸出采用生產者,消費者模式,並構造隊列進行處理,如下圖
這種輸入模型在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中
只說觸摸屏,虛擬鍵類似,觸摸屏調用的是