Android输入输出机制之来龙去脉之前生后世

来源:互联网 发布:洛奇英雄传自我优化 编辑:程序博客网 时间:2024/06/09 13:58

先讲一下基本一般的输入处理方式的知识。一般的输入输出采用生产者,消费者模式,并构造队列进行处理,如下图

   

这种输入模型在android的系统中很多地方采用,先从最底层说起:

 为了由于触屏事件频率很高,android设计者讲一个循环线程,拆分为两级循环,并做了个队列来进行缓冲。


InputDispatcherThread和InputReaderThread。InputDispatcherThread在自己的循环中对InputReaderThread请求同步,InputReaderThread收到同步信号后,把事件放入InputDispatcher的队列中。

具体代码如下:

InputReader.cpp中有很多InputMapper,有SwitchInputMapper,KeyBoardInputMapper,TrackballInputMapper,SingleTouchInputMapper,

MultiTouchInputMapper。当线程从EventHub读取到Event后,调用这些InputMapper的pocess方法:

 

Java代码  收藏代码
  1. 文件InputReader.cpp中:  
  2.   
  3. bool InputReaderThread::threadLoop() {  
  4.     mReader->loopOnce();  
  5.     return true;  
  6. }  
  7.   
  8.   
  9. void InputReader::loopOnce() {  
  10.     RawEvent rawEvent;  
  11.     mEventHub->getEvent(& rawEvent);  
  12.   
  13. #if DEBUG_RAW_EVENTS  
  14.     LOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d",  
  15.             rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,  
  16.             rawEvent.value);  
  17. #endif  
  18.   
  19.     process(& rawEvent);  
  20. }  

 process如下

  void InputReader::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    default:
        consumeEvent(rawEvent);
        break;
    }
}

Java代码  收藏代码
  1. void InputReader::process(const RawEvent* rawEvent) {  
  2.     switch (rawEvent->type) {  
  3.         consumeEvent(rawEvent);  
  4.         break;  
  5.     }  
  6. }  

 

 

Java代码  收藏代码
  1. consumeEvent(rawEvent);  

方法是关键,下面继续跟进;

 

Java代码  收藏代码
  1. void InputReader::consumeEvent(const RawEvent* rawEvent) {  
  2.     int32_t deviceId = rawEvent->deviceId;  
  3.   
  4.     {   
  5.         device->process(rawEvent);  
  6.     } // release device registry reader lock  
  7. }  

   device->process(rawEvent)行, 跟进去:

Java代码  收藏代码
  1. void InputDevice::process(const RawEvent* rawEvent) {  
  2.     size_t numMappers = mMappers.size();  
  3.     for (size_t i = 0; i < numMappers; i++) {  
  4.         InputMapper* mapper = mMappers[i];  
  5.         mapper->process(rawEvent);  
  6.     }  
  7. }  

 下面进入了IputMapper,InputMapper是个纯虚类,process是个纯虚方法,随便找个例子跟进去:

 

Cpp代码  收藏代码
  1. void SingleTouchInputMapper::process(const RawEvent* rawEvent) {  
  2.     switch (rawEvent->type) {  
  3.     case EV_KEY:  
  4.         switch (rawEvent->scanCode) {  
  5.         case BTN_TOUCH:  
  6.             mAccumulator.fields |= Accumulator::FIELD_BTN_TOUCH;  
  7.             mAccumulator.btnTouch = rawEvent->value != 0;  
  8.             // Don't sync immediately.  Wait until the next SYN_REPORT since we might  
  9.             // not have received valid position information yet.  This logic assumes that  
  10.             // BTN_TOUCH is always followed by SYN_REPORT as part of a complete packet.  
  11.             break;  
  12.         }  
  13.         break;  
  14.     case EV_SYN:  
  15.         switch (rawEvent->scanCode) {  
  16.         case SYN_REPORT:  
  17.             sync(rawEvent->when);  
  18.             break;  
  19.         }  
  20.         break;  
  21.     }  
  22. }  

 最关键的是

Cpp代码  收藏代码
  1. sync(rawEvent->when);  

  展开如下:

 

Cpp代码  收藏代码
  1. void SingleTouchInputMapper::sync(nsecs_t when) {  
  2.   
  3.     syncTouch(when, true);  
  4.   
  5.    
  6. }  

 

Java代码  收藏代码
  1. void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {  
  2.      
  3.     if (touchResult == DISPATCH_TOUCH) {  
  4.         detectGestures(when);  
  5.         dispatchTouches(when, policyFlags);  
  6.     }  
  7. }  

 

 这两行,一个是虚拟键盘,一个是触摸屏。

      TouchResult touchResult = consumeOffScreenTouches(when, policyFlags);

Java代码  收藏代码
  1. dispatchTouches  

    只说触摸屏,虚拟键类似,触摸屏调用的是  

 

Cpp代码  收藏代码
  1. void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {  
  2.         // Dispatch pointer down events using the new pointer locations.  
  3.         while (!downIdBits.isEmpty()) {  
  4.             dispatchTouch(when, policyFlags, &mCurrentTouch,  
  5.                     activeIdBits, downId, pointerCount, motionEventAction);  
  6.         }  
  7.     }  
  8. }  

 

Cpp代码  收藏代码
  1. dispatchTouch(when, policyFlags, &mCurrentTouch,  
  2.                     activeIdBits, downId, pointerCount, motionEventAction);  

这个方法展开如下:

 

Java代码  收藏代码
  1. void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags,  
  2.         TouchData* touch, BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,  
  3.         int32_t motionEventAction) {  
  4.     int32_t pointerIds[MAX_POINTERS];  
  5.     PointerCoords pointerCoords[MAX_POINTERS];  
  6.     int32_t motionEventEdgeFlags = 0;  
  7.     float xPrecision, yPrecision;  
  8.   
  9.     {   
  10.     getDispatcher()->notifyMotion(when, getDeviceId(), getSources(), policyFlags,  
  11.             motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags,  
  12.             pointerCount, pointerIds, pointerCoords,  
  13.             xPrecision, yPrecision, mDownTime);  
  14. }  

 

  这样就到了InputDiaptcher的notifyMotion方法,这个方法很长,都再处理MOVE事件,将无用的删除后,留下如下关键代码:

 

Java代码  收藏代码
  1.  void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,  
  2.         uint32_t policyFlags, int32_t action, int32_t flags, int32_t metaState, int32_t edgeFlags,  
  3.         uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,  
  4.         float xPrecision, float yPrecision, nsecs_t downTime) {        
  5.   
  6.  // Just enqueue a new motion event.  
  7.         MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,  
  8.                 deviceId, source, policyFlags, action, flags, metaState, edgeFlags,  
  9.                 xPrecision, yPrecision, downTime,  
  10.                 pointerCount, pointerIds, pointerCoords);  
  11.   
  12.         needWake = enqueueInboundEventLocked(newEntry);  
  13. }  

 最后一句:

Java代码  收藏代码
  1. needWake = enqueueInboundEventLocked(newEntry);  

 

Cpp代码  收藏代码
  1. bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {  
  2.     bool needWake = mInboundQueue.isEmpty();  
  3.     mInboundQueue.enqueueAtTail(entry);  
  4.   
  5.     switch (entry->type) {  
  6.     case EventEntry::TYPE_KEY: {  
  7.         KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);  
  8.         if (isAppSwitchKeyEventLocked(keyEntry)) {  
  9.             if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {  
  10.                 mAppSwitchSawKeyDown = true;  
  11.             } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {  
  12.                 if (mAppSwitchSawKeyDown) {  
  13. #if DEBUG_APP_SWITCH  
  14.                     LOGD("App switch is pending!");  
  15. #endif  
  16.                     mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;  
  17.                     mAppSwitchSawKeyDown = false;  
  18.                     needWake = true;  
  19.                 }  
  20.             }  
  21.         }  
  22.         break;  
  23.     }  
  24.     }  
  25.   
  26.     return needWake;  
  27. }  

 

Cpp代码  收藏代码
  1. mInboundQueue正是上面所说的队列。到此为止,从InputReader插入到队列就完成了。  

 那么InputDispatcher又是如何从队列中取出来的呢?累了。

  InputDiapather的

Cpp代码  收藏代码
  1. dispatchOnce  

方法如下:

Cpp代码  收藏代码
  1. void InputDispatcher::dispatchOnce() {  
  2.     nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();  
  3.     nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();  
  4.   
  5.     nsecs_t nextWakeupTime = LONG_LONG_MAX;  
  6.     { // acquire lock  
  7.         AutoMutex _l(mLock);  
  8.         dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);  
  9.   
  10.         if (runCommandsLockedInterruptible()) {  
  11.             nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately  
  12.         }  
  13.     } // release lock  
  14.   
  15.     // Wait for callback or timeout or wake.  (make sure we round up, not down)  
  16.     nsecs_t currentTime = now();  
  17.     int32_t timeoutMillis;  
  18.     if (nextWakeupTime > currentTime) {  
  19.         uint64_t timeout = uint64_t(nextWakeupTime - currentTime);  
  20.         timeout = (timeout + 999999LL) / 1000000LL;  
  21.         timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout);  
  22.     } else {  
  23.         timeoutMillis = 0;  
  24.     }  
  25.   
  26.     mLooper->pollOnce(timeoutMillis);  
  27. }  

 最关键的是

Cpp代码  收藏代码
  1. dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);  

Cpp代码  收藏代码
  1. mLooper->pollOnce(timeoutMillis);//这个是个回调。  
Cpp代码  收藏代码
  1.   

代码又长又臭

 

 

Java代码  收藏代码
  1. void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,  
  2.         nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) {  
  3.     case EventEntry::TYPE_MOTION: {  
  4.         MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);  
  5.         if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {  
  6.             dropReason = DROP_REASON_APP_SWITCH;  
  7.         }  
  8.         done = dispatchMotionLocked(currentTime, typedEntry,  
  9.                 &dropReason, nextWakeupTime);  
  10.         break;  
  11.     }  
  12.   
  13. }  

 

Java代码  收藏代码
  1. 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);

 

    然后// Send the dispatch signal.
    status = connection->inputPublisher.sendDispatchSignal();
    if (status) {
        LOGE("channel '%s' ~ Could not send dispatch signal, status=%d",
                connection->getInputChannelName(), status);
        abortBrokenDispatchCycleLocked(currentTime, connection);
        return;
    }

至此,InputDisapatcher也结束了。

 

既然发布出去,必然有订阅者:在InputTransport.cpp中

Java代码  收藏代码
  1. status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) {  
  2. #if DEBUG_TRANSPORT_ACTIONS  
  3.     LOGD("channel '%s' consumer ~ consume",  
  4.             mChannel->getName().string());  
  5. #endif  
  6.   
  7.     *outEvent = NULL;  
  8.   
  9.     int ashmemFd = mChannel->getAshmemFd();  
  10.     int result = ashmem_pin_region(ashmemFd, 00);  
  11.     if (result != ASHMEM_NOT_PURGED) {  
  12.         if (result == ASHMEM_WAS_PURGED) {  
  13.             LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d because it was purged "  
  14.                     "which probably indicates that the publisher and consumer are out of sync.",  
  15.                     mChannel->getName().string(), result, ashmemFd);  
  16.             return INVALID_OPERATION;  
  17.         }  
  18.   
  19.         LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d.",  
  20.                 mChannel->getName().string(), result, ashmemFd);  
  21.         return UNKNOWN_ERROR;  
  22.     }  
  23.   
  24.     if (mSharedMessage->consumed) {  
  25.         LOGE("channel '%s' consumer ~ The current message has already been consumed.",  
  26.                 mChannel->getName().string());  
  27.         return INVALID_OPERATION;  
  28.     }  
  29.   
  30.     // Acquire but *never release* the semaphore.  Contention on the semaphore is used to signal  
  31.     // to the publisher that the message has been consumed (or is in the process of being  
  32.     // consumed).  Eventually the publisher will reinitialize the semaphore for the next message.  
  33.     result = sem_wait(& mSharedMessage->semaphore);  
  34.     if (result < 0) {  
  35.         LOGE("channel '%s' consumer ~ Error %d in sem_wait.",  
  36.                 mChannel->getName().string(), errno);  
  37.         return UNKNOWN_ERROR;  
  38.     }  
  39.   
  40.     mSharedMessage->consumed = true;  
  41.   
  42.     switch (mSharedMessage->type) {  
  43.     case AINPUT_EVENT_TYPE_KEY: {  
  44.         KeyEvent* keyEvent = factory->createKeyEvent();  
  45.         if (! keyEvent) return NO_MEMORY;  
  46.   
  47.         populateKeyEvent(keyEvent);  
  48.   
  49.         *outEvent = keyEvent;  
  50.         break;  
  51.     }  
  52.   
  53.     case AINPUT_EVENT_TYPE_MOTION: {  
  54.         MotionEvent* motionEvent = factory->createMotionEvent();  
  55.         if (! motionEvent) return NO_MEMORY;  
  56.   
  57.         populateMotionEvent(motionEvent);  
  58.   
  59.         *outEvent = motionEvent;  
  60.         break;  
  61.     }  
  62.   
  63.     default:  
  64.         LOGE("channel '%s' consumer ~ Received message of unknown type %d",  
  65.                 mChannel->getName().string(), mSharedMessage->type);  
  66.         return UNKNOWN_ERROR;  
  67.     }  
  68.   
  69.     return OK;  
  70. }  

 也许我们最关心的是如何订阅的,不得不取看一下JNI的代码,文件android_view_InputQueue.cpp

 聚焦到这里

 

Cpp代码  收藏代码
  1. status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj,  
  2.         jobject inputHandlerObj, jobject messageQueueObj) {  
  3.     sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,  
  4.             inputChannelObj);  
  5.     if (inputChannel == NULL) {  
  6.         LOGW("Input channel is not initialized.");  
  7.         return BAD_VALUE;  
  8.     }  
  9.   
  10. #if DEBUG_REGISTRATION  
  11.     LOGD("channel '%s' - Registered", inputChannel->getName().string());  
  12. #endif  
  13.   
  14.     sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);  
  15.   
  16.     { // acquire lock  
  17.         AutoMutex _l(mLock);  
  18.   
  19.         if (getConnectionIndex(inputChannel) >= 0) {  
  20.             LOGW("Attempted to register already registered input channel '%s'",  
  21.                     inputChannel->getName().string());  
  22.             return BAD_VALUE;  
  23.         }  
  24.   
  25.         uint16_t connectionId = mNextConnectionId++;  
  26.         sp<Connection> connection = new Connection(connectionId, inputChannel, looper);  
  27.         status_t result = connection->inputConsumer.initialize();  
  28.         if (result) {  
  29.             LOGW("Failed to initialize input consumer for input channel '%s', status=%d",  
  30.                     inputChannel->getName().string(), result);  
  31.             return result;  
  32.         }  
  33.   
  34.         connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj);  
  35.   
  36.         int32_t receiveFd = inputChannel->getReceivePipeFd();  
  37.         mConnectionsByReceiveFd.add(receiveFd, connection);  
  38.   
  39.         looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);  
  40.     } // release lock  
  41.   
  42.     android_view_InputChannel_setDisposeCallback(env, inputChannelObj,  
  43.             handleInputChannelDisposed, this);  
  44.     return OK;  
  45. }  

 

 

  也许更想知道的是消息队列在什么地方,进入InputQueue.java来看

 

Java代码  收藏代码
  1. public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler,  
  2.         MessageQueue messageQueue) {  
  3.     if (inputChannel == null) {  
  4.         throw new IllegalArgumentException("inputChannel must not be null");  
  5.     }  
  6.     if (inputHandler == null) {  
  7.         throw new IllegalArgumentException("inputHandler must not be null");  
  8.     }  
  9.     if (messageQueue == null) {  
  10.         throw new IllegalArgumentException("messageQueue must not be null");  
  11.     }  
  12.       
  13.     synchronized (sLock) {  
  14.         if (DEBUG) {  
  15.             Slog.d(TAG, "Registering input channel '" + inputChannel + "'");  
  16.         }  
  17.           
  18.         nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue);  
  19.     }  
  20. }  

 

在ViewRoot.java中有这么几行

 

Cpp代码  收藏代码
  1. InputQueue.registerInputChannel(mInputChannel, mInputHandler,  
  2.         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代码  收藏代码
  1.       
  2. 回调的java代码的方法入口为:InputQueue.java中的。  
  3.   
  4. @SuppressWarnings("unused")  
  5.     private static void dispatchMotionEvent(InputHandler inputHandler,  
  6.             MotionEvent event, long finishedToken) {  
  7.         Runnable finishedCallback = FinishedCallback.obtain(finishedToken);  
  8.         inputHandler.handleMotion(event, finishedCallback);  
  9.     }  

 这样就回调到了ViewRoot中