Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 5.0(Lollipop)事件輸入系統(Input System)

Android 5.0(Lollipop)事件輸入系統(Input System)

編輯:關於Android編程

其實Android 5.0中事件輸入子系統的框架和流程沒有本質變化。Service端的實現在/frameworks/native/services/inputflinger/下(4.4中在/frameworks/base/services/input下)。通用部分的實現在/frameworks/native/libs/input/下。Android系統中負責管理輸入事件的主要是InputManagerService(IMS)。它主要的任務就是從設備中讀事件數據,然後將輸入事件發送到焦點窗口中去,另外還需要讓系統有機會來處理一些系統按鍵。顯然,要完成這個工作,IMS需要與其它模塊打交道,其中最主要的就是WMS和ViewRootImpl。主要的幾個模塊示意如下:

vcPmvavKwrz+zai5/UlucHV0TWFuYWdlcqOsSW5wdXRNb25pdG9y0rvCt7SruPhQaG9uZVdpbmRvd01hbmFnZXLAtNf2z7XNs8rkyOvKwrz+tcS0psDto6zB7dK7t73D5r2r1eLQqcrCvP60q7j4vbm147ywvODK07Swv9qho05hdGl2ZUlucHV0TWFuYWdlcsq1z9ZJbnB1dFJlYWRlclBvbGljeUludGVyZmFjZbrNSW5wdXREaXNwYXRjaGVyUG9saWN5SW50ZXJmYWNlvdO/2qOs1NpOYXRpdmWy47XESW5wdXRNYW5hZ2Vyus1KYXZhsuO1xElNU7zkxvC1vdK7uPa9usuusuO1xNf308Oho0lucHV0TW9uaXRvcsq1z9bBy1dpbmRvd01hbmFnZXJDYWxsYmFja3O907/ao6zG8LW9wctJTVO1vVdNU7XEway909f308Oho0FwcNXisd+jrFZpZXdSb290SW1wbM/gtbHT2kFwcLbL0ru49ralsuNWaWV3tcRDb250cm9sbGVyoaPV4rj2tqWy41ZpZXfU2ldNU9bQttTTptK7uPa0sL/ao6zTw1dpbmRvd1N0YXRlw+jK9qGjV2luZG93U3RhdGXW0NPQSW5wdXRXaW5kb3dIYW5kbGW0+rHt0ru49r3TytXK5MjrysK8/rXEtLC/2r7ksfqho0lucHV0RGlzcGF0Y2hlctbQtcRtRm9jdXNlZFdpbmRvd0hhbmRsZda4yr7By725teO0sL/atcS+5LH6oaNJbnB1dERpc3BhdGNoZXK53MDtwcvSu9vnway906Oo0ru49sGsvdO21NOm0ru49teisuG1vVdNU7XEtLC/2qOpo6zNqLn91eLQqbj2way900lucHV0RGlzcGF0Y2hlcr/J0tTWsb3TvavK5MjrysK8/reizflBcHC2y7XEvbm147Swv9qho8rkyOvKwrz+tNNEcml2ZXK/qsq8tcS0psDtuf2zzLTz1sLI58/Co7o8YnI+CjwvcD4KPHA+PGltZyBzcmM9"/uploadfile/Collfiles/20141215/2014121508324848.png" alt="\">

為了故事的完整性,還是先看下初始化。SystemServer中初始化IMS,然後初始化WMS,把IMS作為參數傳入。

470            Slog.i(TAG, "Input Manager");
471            inputManager = new InputManagerService(context);
472
473            Slog.i(TAG, "Window Manager");
474            wm = WindowManagerService.main(context, inputManager,
475                    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
476                    !mFirstBoot, mOnlyCore);
...
483            inputManager.start();

IMS的構造函數中,調用nativeInit()來初始化。注意這裡拿了DisplayThread的Handler,意味著IMS中的消息隊列處理都是在單獨的DisplayThread中進行的。它是系統中共享的單例前台線程,主要用作輸入輸出的處理用。這樣可以使用戶體驗敏感的處理少受其它工作的影響,減少延時。整個初始化過程流程如下:

\

可以看到,初始化時依次初始化NativeInputManager,EventHub,InputManager, InputDispatcher,InputReader,InputReaderThread, InputDispatcherThread。NativeInputManager可看作IMS和InputManager的中間層,將IMS的請求轉化為對InputManager及其內部對象的操作,同時將InputManager中模塊的請求通過JNI調回IMS。InputManager是輸入控制中心,它有兩個關鍵線程InputReaderThread和InputDispatcherThread,它們的主要功能部分分別在InputReader和InputDispacher。前者用於從設備中讀取事件,後者將事件分發給目標窗口。EventHub是輸入設備的控制中心,它直接與input driver打交道。負責處理輸入設備的增減,查詢,輸入事件的處理並向上層提供getEvents()接口接收事件。在它的構造函數中,主要做三件事:
1. 創建epoll對象,之後就可以把各輸入設備的fd掛在上面多路等待輸入事件。
2. 建立用於喚醒的pipe,把讀端掛到epoll上,以後如果有設備參數的變化需要處理,而getEvents()又阻塞在設備上,就可以調用wake()在pipe的寫端寫入,就可以讓線程從等待中返回。
3. 利用inotify機制監聽/dev/input目錄下的變更,如有則意味著設備的變化,需要處理。
因為事件的處理是流水線,需要InputReader先讀事件,然後InputDispatcher才能進一步處理和分發。因此InputDispatcher需要監聽InputReader。這裡使用了Listener模式,InputDispacher作為InputReader構造函數的第三個參數,它實現InputListenerInterface接口。到了InputReader的構造函數中,將之包裝成QueuedInputListener。QueuedInputListener中的成員變量mArgsQueue是一個緩沖隊列,只有在flush()時,才會一次性通知InputDispatcher。QueuedInputListener應用了Command模式,它通過包裝InputDispatcher(實現InputListenerInterface接口),將事件的處理請求封裝成NotifyArgs,使其有了緩沖執行的功能。

全初始化好後,SystemServer調用start()函數讓InputManager中兩個線程開始運行。先看InputReaderThread,它是事件在用戶態處理過程的起點。這裡以按鍵事件的處理為例。

\

InputReaderThread不斷調用InputReader的pollOnce()->getEvents()函數來得到事件,這些事件可以是輸入事件,也可以是由inotify監測到設備增減變更所觸發的事件。第一次進入時會掃描/dev/input目錄建立設備列表,存在mDevice成員變量中(EventHub中有設備列表KeyedVector mDevices;對應的,InputReader中也有設備列表KeyedVector mDevices。這裡先添加到前者,然後會在InputReader::addDeviceLocked()中添加到後者。),同時將增加的fd加到epoll的等待集合中。在接下來的epoll_wait()等待時,如果有事件就會返回,同時返回可讀事件數量。在這裡,從Input driver讀出的事件從原始的input_event結構轉為RawEvent結構,放到getEvents()的輸出參數buffer中。getEvents()返回後,InputReader調用processEventsLocked()處理事件,對於設備改變,會根據實際情況調用addDeviceLocked(), removeDeviceLocked()和handleConfigurationChangedLocked()。對於其它設備中來的輸入事件,會調用processEventsForDeviceLocked()進一步處理。其中會根據當時注冊的InputMapper對事件進行處理,然後將事件處理請求放入緩沖隊列(QueuedInputListener中的mArgsQueue)。

155void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
156    mArgsQueue.push(new NotifyKeyArgs(*args));
157}
在InputReader的loopOnce()的結尾會調用QueuedInputListener::flush()統一回調緩沖隊列中各元素的notify()接口:
171void QueuedInputListener::flush() {
172    size_t count = mArgsQueue.size();
173    for (size_t i = 0; i < count; i++) {
174        NotifyArgs* args = mArgsQueue[i];
175        args->notify(mInnerListener);
176        delete args;
177    }
178    mArgsQueue.clear();
179}

以按鍵事件為例,最後會調用到InputDispatcher的notifyKey()函數中。這裡先將參數封裝成KeyEvent:

2416    KeyEvent event;
2417    event.initialize(args->deviceId, args->source, args->action,
2418            flags, keyCode, args->scanCode, metaState, 0,
2419            args->downTime, args->eventTime);
然後把它作為參數調用NativeInputManager的interceptKeyBeforeQueueing()函數。顧名思義,就是在放到待處理隊列前看看是不是需要系統處理的系統按鍵,它會通過JNI調回Java世界,最終調到PhoneWindowManager的interceptKeyBeforeQueueing()。然後,基於輸入事件信息創建KeyEntry對象,調用enqueueInboundEventLocked()將之放入隊列等待InputDiaptcherThread線程拿出處理。
2439        KeyEntry* newEntry = new KeyEntry(args->eventTime,
2440                args->deviceId, args->source, policyFlags,
2441                args->action, flags, keyCode, args->scanCode,
2442                metaState, repeatCount, args->downTime);
2443
2444        needWake = enqueueInboundEventLocked(newEntry);
下面該InputDispatcherThread登場了。

\

可以看到,InputDisptacher的主要任務是把前面收到的輸入事件發送到PWM及App端的焦點窗口。前面提到InputReaderThread中收到事件後會調用notifyKey()來通知InputDispatcher,也就是放在mInboundQueue中,在InputDispatcher的dispatchOnce()函數中,會從這個隊列拿出處理。

234        if (!haveCommandsLocked()) {
235            dispatchOnceInnerLocked(&nextWakeupTime);
236        }
...
240        if (runCommandsLockedInterruptible()) {
241            nextWakeupTime = LONG_LONG_MIN;
242        }

其中dispatchOnceInnerLocked()會根據拿出的EventEntry類型調用相應的處理函數,以Key事件為例會調用dispatchKeyLocked():

767            CommandEntry* commandEntry = postCommandLocked(
768                    & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
769            if (mFocusedWindowHandle != NULL) {
770                commandEntry->inputWindowHandle = mFocusedWindowHandle;
771            }
772            commandEntry->keyEntry = entry;
...
791    // Identify targets.
792    Vector inputTargets;
793    int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
794            entry, inputTargets, nextWakeupTime);
..
804    addMonitoringTargetsLocked(inputTargets);
805
806    // Dispatch the key.
807    dispatchEventLocked(currentTime, entry, inputTargets);
它會找到目標窗口,然後通過之前和App間建立的連接發送事件。如果是個需要系統處理的Key事件,這裡會封裝成CommandEntry插入到mCommandQueue隊列中,後面的runCommandLockedInterruptible()函數中會調用doInterceptKeyBeforeDispatchingLockedInterruptible()來讓PWM有機會進行處理。最後dispatchOnce()調用pollOnce()從和App的連接上接收處理完成消息。那麼,InputDispatcher是怎麼確定要往哪個窗口中發事件呢?這裡的成員變量mFocusedWindowHandle指示了焦點窗口,然後findFocusedWindowTargetsLocked()會調用一系列函數(handleTargetsNotReadyLocked(), checkInjectionPermission(), checkWindowReadyForMoreInputLocked()等)檢查mFocusedWindowHandle是否能接收輸入事件。如果可以,將之以InputTarget的形式加到目標窗口數組中。然後就會調用dispatchEventLocked()進行發送。那麼,這個mFocusedWindowHandle是如何維護的呢?為了更好地理解,這裡回頭分析下窗口連接的管理及焦點窗口的管理。


在App端,新的頂層窗口需要被注冊到WMS中,這是在ViewRootImpl::setView()中做的。

\

其中與輸入相關的主要有以下幾步:
先創建InputChannel,注意還沒初始化。

521                     mInputChannel = new InputChannel();
初始化InputChannel,通過WMS的相應接口addToDisplay():
527                     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
528                             getHostVisibility(), mDisplay.getDisplayId(),
529                             mAttachInfo.mContentInsets, mInputChannel);
WMS會建立與InputDispatcher的連接。流程如下:

\

ViewRootImpl通過Session中的addToDisplay()會最終調用WMS的addWindow()。在WMS中,會創建一對InputChannel,本質上是一對本地socket。然後一個注冊給InputDispatcher,一個作為輸出參數傳回給App的ViewRootImpl。這樣就建立了App與IMS的一對連接。

2409            if (outInputChannel != null && (attrs.inputFeatures
2410                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
2411                String name = win.makeInputChannelName();
2412                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
2413                win.setInputChannel(inputChannels[0]);
2414                inputChannels[1].transferTo(outInputChannel);
2415
2416                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
2417            }
在InputDispatcher::registerInputChannel()中:
3327        sp connection = new Connection(inputChannel, inputWindowHandle, monitor);
3328
3329        int fd = inputChannel->getFd();
3330        mConnectionsByFd.add(fd, connection);
...
3336        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
這裡創建的Connection表示一個InputDispatcher到應用窗口的連接,裡邊除了用於傳輸的inputChannel,inputPublisher和表示事件接收窗口的inputWindowHandle,還有兩個隊列,outboundQueue是要發的事件,waitQueue是已發事件但還沒有從App端收到完成通知的。這是因為對於一些事件,Input Dispatcher在App沒處理完前一個時不會發第二個。mLooper->addFd()將相應的fd放入InputDispatcher等待的集合中,回調函數為handleReceiveCallback(),也就是說InputDispatcher在收到App發來的消息時是調用它進行處理的。最後調用mLooper->wake()使InputDispatcherThread從epoll_wait()中返回。

回到App端,如果前面沒啥問題,接下來會創建WindowInputEventReceiver,它是App的事件接收端。
 607                     mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
 608                             Looper.myLooper());
初始化完後,這個連接的fd就被掛到主線程的等待fd集合去了(InputEventReceiver::nativeInit())。也就是說,當連接上有消息來,主線程就會調用相應的回調處理NativeInputEventReceiver::handleEvent()。

接下來初始化App端事件處理的流水線,這裡使用了Chain of responsibility模式,讓事件經過各個InputStage,每一個Stage可以決定是否自己處理,也可以傳遞給下一家。下一家也是如此。在後面的handleEvent()可以看到它們的用法。
 623                 // Set up the input pipeline.
 624                 CharSequence counterSuffix = attrs.getTitle();
 625                 mSyntheticInputStage = new SyntheticInputStage();
 626                 InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
 627                 InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
 628                         "aq:native-post-ime:" + counterSuffix);
 629                 InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
 630                 InputStage imeStage = new ImeInputStage(earlyPostImeStage,
 631                         "aq:ime:" + counterSuffix);
 632                 InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
 633                 InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
 634                         "aq:native-pre-ime:" + counterSuffix);
 635
 636                 mFirstInputStage = nativePreImeStage;
 637                 mFirstPostImeInputStage = earlyPostImeStage;
到這裡,可以知道,InputDispatcher會維護和WMS中所有窗口的連接,雖然一般只會往焦點窗口發事件。如下所示。

\

連接建立後,接下來要考慮WMS如何將焦點窗口信息傳給InputDispatcher。舉例來說,當新的窗口加入到WMS中,一般焦點會放到新加窗口上。來看下WMS中的addWindow()函數。

\

首先,當焦點需要變化時。當焦點窗口變化時,WMS調用

mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
將焦點窗口設到InputMonitor的mInputFocus中。然後調用
mInputMonitor.updateInputWindowsLw(true);
來創建InputWindowHandle列表,其中被設成焦點窗口的InputWindowHandle的hasFocus會被置位。之後會調用
mService.mInputManager.setInputWindows(mInputWindowHandles);
將這些信息傳到Native層的InputDispatcher。這樣InputDispatcher就能夠知道要往哪個窗口傳事件。在InputDispatcher的setInputWindows()中,會更新InputDispatcher中的焦點窗口句柄。這樣,InputDispatcher中就記錄下了焦點窗口信息。當IMS的InputDispatcher通過InputChannel發事件到焦點窗口時,NativeInputEventReceiver的handleEvent()會被調用。

\

基本流程比較直觀,先接收事件,然後放入ViewRootImpl的處理隊列,然後dispatch給View處理,經過上面提到的一系列InputStage,最後App處理完事件後還需要向IMS發送一個完成信號。

注意上面是以Key事件為例的。對於Motion事件就有差別了。因為觸摸移動中的事件不一定要每一個都處理,因為顯示也就60HZ,你如果100HZ的輸入事件,全處理只會浪費計算資源。上面這條路是每當InputDispatcher有事件發過來時就會觸發的,而對於Motion事件,系統會把一個VSync周期內的事件存為Batch,當VSync到來時一起處理。從JB開始,App對輸入事件的處理是由VSync信號來驅動的。可以看Choreographer中的VSync回調中首先處理的就是輸入事件。

542        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
543        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
544        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
在非VSync觸發的情況下,NativeInputEventReceiver::handleEvent()調用consumeEvents()時參數consumeBatches為false,通過InputConsumper::consume()函數得知,它會被放到Batch當中:

448                if (canAddSample(batch, &mMsg)) {
449                    batch.samples.push(mMsg);
循環退出條件不滿足所以一直讀到receiveMessage()失敗,退出後在consumeEvents()中由於返回值不為0所以事件不會被馬上處理;而當VSync信號到來時,下面流程會被觸發,這裡consumeEvents()的參數consumeBatches為true,意味著要處理Batch。

\

ViewRootImpl中維護了pending input event的列表,用mPendingInputEventHead和mPendingInputEventTail指示,其中的元素為QueuedInputEvent類型。這個隊列會由InputEventReceiver調用enqueueInputEvent()加入元素,然後ViewRootImpl延時或非延時在doProcessInputEvents()中讀出並處理。處理的方法如前圖調用deliverInputEvent(),然後調用InputStage的deliver()方法進行處理。最後調用finishInputEvent()向IMS發送完成信息,它實際是調用了NativeInputEventReceiver::finishInputEvent(),該函數內部使用InputConsumer的sendFinishedSignal()發送處理結束信號。

事實上,當Input resample機制打開時(系統屬性ro_input.noresample控制),對於Motion事件在InputConsumer裡的處理會更復雜一點。Android在InputConsumer中加入了對Motion事件的重采樣。VSync代表Display已完成一幀的顯示,系統需要准備下一幀,也就是說我們需要知道VSync信號來到時觸摸的坐標。那麼問題來了,輸入設備的事件不是按VSync來的,比如是10ms為周期(VSync周期為16.67ms)。那麼怎麼知道VSync的時刻的輸入坐標呢,只能靠估計。怎麼估計呢,靠的是采樣加上內外插值結合的估值方法。詳細可參見http://www.masonchang.com/blog/2014/8/25/androids-touch-resampling-algorithm 。

前面提到,InputDispatcher除了給App端傳事件外,還有個任務是處理系統按鍵。系統中有一些特殊的按鍵,是系統需要處理的,如音量,電源鍵等。它是通過interceptKeyBeforeDispatching()和interceptKeyBeforeQueueing()兩個函數來截獲的。interceptKeyBeforeDispatching()主要用於處理home, menu和search,interceptkeyBeforeQueueing()主要用於處理音量和電源鍵。這些處理的實現都放在PWM中,而調用者是在InputDispatcherThread。這樣的好處是把平台相關的東西都放在PWM中,而InputDispatcher中是平台通用的東西,廠商要定制策略只需修改PWM。這樣,做到了Mechanism和Policy的分離。那InputDisaptcher是如何調用到PWM中的呢?首先InputDispatcher中的代碼中有幾個hook的地方:

3510     nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
3511             &event, entry->policyFlags);
這裡的mPolicy其實是NativeInputManager。NativeInputManager會通過JNI調用到Java世界中的IMS的相應函數:
1463     // Native callback.
1464     private long interceptKeyBeforeDispatching(InputWindowHandle focus,
1465             KeyEvent event, int policyFlags) {
1466         return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event,      policyFlags);
1467     }
這裡的mWindowManagerCallbacks其實是InputMonitor。然後就調用到PWM了。
380     public long interceptKeyBeforeDispatching(
381             InputWindowHandle focus, KeyEvent event, int policyFlags) {
382         WindowState windowState = focus != null ? (WindowState) focus.windowState :     null;
383         return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event,       policyFlags);
384     }
好了,到這裡可以小結一下了。輸入事件從Kernel到App和PWM的旅程大體如下圖所示:

\

可以看到,從Input driver中讀出的是input_event結構,之後在經過中間模塊時會按需對其進行封裝,變成或被封裝進RawEvent, KeyEntry, DispatchEntry, InputMessage, InputEvent, QueuedInputEvent等結構。其中有些結構是對另一些結構的封裝,如DispatchEntry中封裝了KeyEntry,QueuedInputEvent是對InputEvent的封裝等。

回到主線,故事來沒講完。上面說到App這端處理完輸入事件,然後通過和IMS中InputDispacher的通信管道InputChannel發了處理完成通知。那InputDispatcher這邊收到後如何處理呢?

\

InputDispatcher會調用handleReceiveCallback()來處理完成信號。這裡先是往Command隊列裡放一個處理事務執行doDispatchCycleFinishedLockedInterruptible(),後面在runCommandsLockedInterruptible()中會取出執行。在doDispatchCycleFinishedLockedInterruptible()函數中,會先調用afterKeyEventLockedInterruptible()。Android中可以定義一些Fallback鍵,即如果一個Key事件App沒有處理,可以Fallback成另外默認的Key事件,這是在這裡的dispatchUnhandledKey()函數中進行處理的。接著InputDispatcher會將該收到完成信號的事件項從等待隊列中移除。同時由於上一個事件已被App處理完,就可以調用startDispatchCycleLocked()來進行下一輪事件的處理了。

3558        // Dequeue the event and start the next cycle.
3559        // Note that because the lock might have been released, it is possible that the
3560        // contents of the wait queue to have been drained, so we need to double-check
3561        // a few things.
3562        if (dispatchEntry == connection->findWaitQueueEntry(seq)) {
3563            connection->waitQueue.dequeue(dispatchEntry);
...
3571        }
3572
3573        // Start the next dispatch cycle for this connection.
3574        startDispatchCycleLocked(now(), connection);
startDispatchCycleLocked函數會檢查相應連接的輸出緩沖中(connection->outboundQueue)是否有事件要發送的,有的話會通過InputChannel發送出去。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved