Android 2.3 input輸入事件處理

linux內核提供了一個Input子系統來實現的,Input子系統會在/dev/input/路徑下創建我們硬件輸入設備的節點,一般情況下在我們的手機中這些節點是以eventXX來命名的,如event0,event1等等,可以利用EVIOCGNAME獲取此事件結點名稱。這就是android中對於input事件處理數據的來源點,至於驅動寫入數據這塊就不說了。

首先,簡而言之的介紹一下android事件傳遞的流程,按鍵,觸屏等事件是經由WindowManagerService獲取,並通過共享內存和管道的方式傳遞給ViewRoot,ViewRoot再dispatch給Application的View。當有事件從硬件設備輸入時,system_server端在檢測到事件發生時,通過管道(pipe)通知ViewRoot事件發生,此時ViewRoot再去的內存中讀取這個事件信息。下面以一個模塊劃分圖瞭解一下整個過程:



下面詳細介紹一個各個模塊主要處理流程:

1、建立通讀通道初始化:

A、WindowManagerService與ViewRoot建立管道初始化

WindowManagerService : 主要負責事件傳遞,運行於system_server中,主要利用inputmanager啓動input事件啓動線程

讀取event數據

WindowManagerService--->ViewRoot方向的管道通信,表示WMS通知ViewRoot有新事件被寫入到共享內存;

     ViewRoot-->WindowManagerService方向的管道通信,表示ViewRoot已經消化完共享內存中的新事件,特此通知WMS。

     ViewRoot和WindowManagerService的管道的文件描述符都是被存儲在一個名爲InputChannel的類中,這個InputChannel類是

管道通信的載體。而這兩者間通過ashmem_create_region創建匿名內存進行數據的傳遞 。

ViewRoot.java 端建立管道:

mInputChannel = new InputChannel();
                try {
                    res = sWindowSession.add(mWindow, mWindowAttributes,
                            getHostVisibility(), mAttachInfo.mContentInsets,
                            mInputChannel);
                } catch (RemoteException e)

WindowManagerService.java 建立管道:

if (outInputChannel != null) {
                String name = win.makeInputChannelName();
                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
                win.mInputChannel = inputChannels[0];
                inputChannels[1].transferToBinderOutParameter(outInputChannel);
                
                mInputManager.registerInputChannel(win.mInputChannel);
            }

創建一對InputChannel,這一對InputChannel中實現了一組全雙工管道及申請共享內存,outInputChannel爲ViewRoot傳遞來的

InputChannel對象,在addWindow中對其進行賦值。如此兩者利用pipe建立控制通道,利用共享內存建立數據通道。

這是涉及到的文件如下:

frameworks/base/service/java/com/android/server/WindowManagerService.java

frameworks/base/core/java/android/view/ViewRoot.java

--> jni android_view_InputChannel.cpp

--> InputTransport.cpp

B、InputChannel的註冊過程

一個管道通信只是對應一個Activity的事件處理,也就是當前系統中有多少個Activity就會有多少個全雙工管道,那麼系統需要一個管理者來管理以及調度每一個管道通信,因此我們在創建完InputChannel對象後,需要將其註冊到這個InputManager管理中。

採用Looper來輪詢是否有事件發生,InputManager啓動了2個進程來管理事件發生與傳遞,InputReaderThread和InputDispatcherThread,InputReaderThread進程負責輪詢事件發生; InputDispatcherThread負責dispatch事件。


2、數據處理流程:

Eventhub 從設備/dev/input/eventX中讀取數據:

bool EventHub::getEvent(RawEvent* outEvent)
{

int pollResult = poll(mFDs, mFDCount, -1);

if (pfd.revents & POLLIN) {
                int32_t readSize = read(pfd.fd, mInputBufferData,
                        sizeof(struct input_event) * INPUT_BUFFER_SIZE);

...

}


InputReader 根據mapper處理不同的數據,這裏有SwitchInputMapper、KeyboardInputMapper,TrackballInputMapper,TouchInputMapper,MouseInputMapper 等處理mapper過濾數據

void InputReader::loopOnce() {
    
 RawEvent rawEvent;
    
 mEventHub->getEvent(& rawEvent);

    process(& rawEvent);
}

void InputReader::process(const RawEvent* rawEvent) {

    switch (rawEvent->type) {
    case EventHubInterface::DEVICE_ADDED:
        addDevice(rawEvent->deviceId);
        break;


    case EventHubInterface::DEVICE_REMOVED:
        removeDevice(rawEvent->deviceId);
        break;


    case EventHubInterface::FINISHED_DEVICE_SCAN:
        handleConfigurationChanged(rawEvent->when);
        break;


    default:
        consumeEvent(rawEvent);
        break;
    }
}

分別通過:

    virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
            uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
            int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0;
    virtual void 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) = 0;
    virtual void notifySwitch(nsecs_t when,
            int32_t switchCode, int32_t switchValue, uint32_t policyFlags) = 0;

將數據回調給inputDisplatcher模塊

 

 InputDispatcher 對於接收到的數據進行分發:

InputDispatcherThread線程的輪詢過程dispatchOnce()-->dispatchOnceInnerLocked(), InputDispatcherThread線程不停的執行該操作,以達到輪詢的目的。

處理進行分發給不同的應用
bool InputDispatcherThread::threadLoop() {
dispatchOnceInnerLocked{
case EventEntry::TYPE_KEY:
dispatchKeyLocked(); --> dispatchEventToCurrentInputTargetsLocked
--> prepareDispatchCycleLocked
case EventEntry::TYPE_MOTION:
dispatchMotionLocked();
}
}

dispatchKeyLocked函數,它接下來就調用dispatchEventToCurrentInputTargetsLocked來進一步處理了。把當前需要接受鍵盤事件的Activity窗口添加到mCurrentInputTargets中去了,因此,這裏就分別把它們取出來,然後調用prepareDispatchCycleLocked函數把鍵盤事件分發給它們處理。

inputDispatcherThread處理流程:

inputDispatcherThread的主要操作是分兩塊同時進行的,

    一部分是對InputReader傳遞過來的事件進行dispatch前處理,比如確定focus window,特殊按鍵處理如HOME/ENDCALL等,在預處理完成 後,InputDispatcher會將事件存儲到對應的focus window的outBoundQueue,這個outBoundQueue隊列是InputDispatcher::Connection的成員函數,因此它是和ViewRoot相關的。

    一部分是對looper的輪詢,這個輪詢過程是檢查NativeInputQueue是否處理完成上一個事件,如果NativeInputQueue處理完成事件,它就會向通過管道向InputDispatcher發送消息指示consume完成,只有NativeInputQueue consume完成一個事件,InputDispatcher纔會向共享內存寫入另一個事件。


這裏主要用到了兩個Queue隊列:

Queue<DispatchEntry> outboundQueue; 利用handleReceiveCallback 處理收到的數據

利用inputPublisher中的publishKeyEvent及publishMotionEvent將event寫入到共享內存中。


Queue<EventEntry> mInboundQueue; 處理notify回調來的數據


針對客戶端數據處理邏輯:
InputConsumer 用於消費數據 (InputChannel.cpp中),其中核心的函數是:
status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) {
    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;
    }
    ...
}


JNI 函數處理數據: android_view_InputQueue.cpp
int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {
        /* 先收到信號 */
        status_t status = connection->inputConsumer.receiveDispatchSignal();
      
        /* 再處理數據 */
        status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent);

        /* 轉換成java對象native數據 */

        android_view_KeyEvent_fromNative 及 android_view_MotionEvent_fromNative      

        /*  回調數據給Java層*/

env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,

            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));

}


對於最後C++調用Java的方法說明一下:

首先註冊兩個方法:

env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));
    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz,
            "dispatchKeyEvent",
            "(Landroid/view/InputHandler;Landroid/view/KeyEvent;J)V");


    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz,
            "dispatchMotionEvent",
            "(Landroid/view/InputHandler;Landroid/view/MotionEvent;J)V");


然後在InputQueue.java中有這兩個方法的定義:

env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));
    private static void dispatchKeyEvent(InputHandler inputHandler,
            KeyEvent event, long finishedToken) {
        Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
        inputHandler.handleKey(event, finishedCallback);
    }


    @SuppressWarnings("unused")
    private static void dispatchMotionEvent(InputHandler inputHandler,
            MotionEvent event, long finishedToken) {
        Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
        inputHandler.handleMotion(event, finishedCallback);
    }


env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));


handleKey最後由InputHandler 處理keyevent及motionevent事件


客戶端數據回調機制:

對於每個客戶端註冊了一個InputChannel 

status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj,

添加notify回調函數

        looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); 

 

env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章