android input子系統詳解

  Input子系統是一個龐大的系統,爲了簡單明瞭的介紹該系統,我們採用分模塊以及倒序追蹤的方式來一步步解開它的面紗。  

一、java層事件傳遞過程               


我們從這個button被點擊來研究input子系統中事件的傳遞。

廢話不多說,對button加一個OnTouchListener,在其onTouch方法上加一個斷點,直接利用eclipsedebug工具查看touch事件的傳遞過程(倒序)

MainActivity$1.onTouch(View, MotionEvent) line: 24
Button(View).dispatchTouchEvent(MotionEvent) line: 8582
RelativeLayout(ViewGroup).dispatchTransformedTouchEvent(MotionEvent, boolean, View, int) line: 2519
RelativeLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 2110
FrameLayout(ViewGroup).dispatchTransformedTouchEvent(MotionEvent, boolean, View, int) line: 2519
FrameLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 2110
ActionBarOverlayLayout(ViewGroup).dispatchTransformedTouchEvent(MotionEvent, boolean, View, int) line: 2519
ActionBarOverlayLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 2110
PhoneWindow$DecorView(ViewGroup).dispatchTransformedTouchEvent(MotionEvent, boolean, View, int) line: 2519
PhoneWindow$DecorView(ViewGroup).dispatchTouchEvent(MotionEvent) line: 2110
PhoneWindow$DecorView.superDispatchTouchEvent(MotionEvent) line: 2482
 
PhoneWindow.superDispatchTouchEvent(MotionEvent) line: 1798
 
MainActivity(Activity).dispatchTouchEvent(MotionEvent) line: 2797
 
PhoneWindow$DecorView.dispatchTouchEvent(MotionEvent) line: 2443
PhoneWindow$DecorView(View).dispatchPointerEvent(MotionEvent) line: 8799
 
ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl$QueuedInputEvent) line: 4663
ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl$QueuedInputEvent) line: 4521
ViewRootImpl$ViewPostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) line: 4035
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$QueuedInputEvent) line: 4088
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$QueuedInputEvent) line: 4054
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$AsyncInputStage).forward(ViewRootImpl$QueuedInputEvent) line: 4191
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).apply(ViewRootImpl$QueuedInputEvent, int) line: 4062
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$AsyncInputStage).apply(ViewRootImpl$QueuedInputEvent, int) line: 4248
ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) line: 4035
ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$QueuedInputEvent) line: 4088
ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$QueuedInputEvent) line: 4054
ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).apply(ViewRootImpl$QueuedInputEvent, int) line: 4062
ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) line: 4035
ViewRootImpl.deliverInputEvent(ViewRootImpl$QueuedInputEvent) line: 6464
ViewRootImpl.doProcessInputEvents() line: 6438
ViewRootImpl.enqueueInputEvent(InputEvent, InputEventReceiver, int, boolean) line: 6391
ViewRootImpl$WindowInputEventReceiver.onInputEvent(InputEvent) line: 6623
ViewRootImpl$WindowInputEventReceiver(InputEventReceiver).dispatchInputEvent(int, InputEvent) line: 185
MessageQueue.nativePollOnce(long, int) line: not available [native method]
MessageQueue.next() line: 148
Looper.loop() line: 151
ActivityThread.main(String[]) line: 5637
Method.invoke(Object, Object[], boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 372
ZygoteInit$MethodAndArgsCaller.run() line: 959
ZygoteInit.main(String[]) line: 754


下面對這個關係鏈按着正序進行分析:

首先看一下整個的調用流程圖

 

幾點說明:

1.InputEventReceiveronInputEvent方法被回調,開啓了javatouch事件傳遞,至於InputEventReceiver是被誰回調的,這裏不得而知,我們稍後再分析。

2.InputStage. NativePreImeInputStageViewPreImeInputStageImeInputStageEarlyPostImeInputStageNativePostImeInputStageViewPostImeInputStageSyntheticInputStage構成一個輸入事件責任處理鏈,如果本階段對事件沒有處理,則傳遞到下一個對象進行處理,直至事件被處理。NativePreImeInputStageViewPreImeInputStageImeInputStage三個類用來實現輸入法的按鍵派發和處理,如果事件不傳遞到輸入法服務中,這三個類可以跳過,直接從EarlyPostImeInputStage對象開始處理,在ViewPostImeInputStage對象處理階段調用了主View 對象(對應PhoneWindow中的DecorView對象)的事件提交函數如(dispatchTouchEvent)函數向視圖對象提交輸入事件,在當前窗口的視圖樹中派發事件。

3.MainActivity.  DecorView在傳遞事件過程中首先將事件傳給ActivitydispatchTouchEvent()方法,所以我們可以重寫這個方法來阻止事件繼續傳遞,在手錶項目中爲了屏蔽應用左滑退出的功能,就採用這種方式做到的。

4.ViewGroup. 這是touch事件在層層viewgroup中傳遞的過程,如果某個viewgroup攔截了事件,譬如手錶中交互是左滑退出,這裏面實際上就是SwipeDismissLayout檢測到左滑手勢時回調OnDismissedListeneronDismissed()方法,最終finish當前activity.

下面是app佈局的層級關係hierarchy view

 

這個圖裏面我們可以看出,decorView是根view,SwipeDismisslayout是怎麼進到這裏面來的呢?在5.0之後,只要加上<item name="android:windowSwipeToDismiss">true</item>,我們的應用就有左滑推出的功能。

那到底framework是在哪裏識別這個標籤的呢?

 

這是一個app創建佈局的過程,不細說了。其中generatelayout這個方法就是在加載我們寫的佈局文件之前根據feature添加一些中間的viewgroup,譬如SwipeDismisslayout.

再比如下面是一個帶actionbar的應用的hierarchy view,這是多麼的複雜啊,實際上我們添加的只是最後的Button,framework給我們追加了一個複雜的ActionBarOverlayLayout.

 

 

5.touch事件在ViewGroup中傳遞細節:

 

這裏面需要注意幾點:

i.action_down事件,如果onInterceptTouchEvent返回trueviewgroup就會攔截touch事件,由自己的onTouchEvent方法處理,並且之後的事件不經過onInterceptTouchEvent,直接由onTouchEvent處理

ii. action_down事件,如果子viewonTouchEvent返回false,那麼之後的事件就不會再給這個子view,viewgroup自己的onTouchEvent處理

產生上面兩點的原因是mFirstTouchTarget==null,mFirstTouchTarget可以看成是每次action_down開始接收事件的target,即現在viewGroup沒有找到事件接受者,只能自己處理,intercepted = true

dispatchTouchEvent(MotionEvent ev) {
..............
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
...............
}

iii.isTransformedTouchPointInView判斷touch事件是否在某個view控件範圍之內,只是簡單的比較x,y座標

    protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }
    /**
     * Determines whether the given point, in local coordinates is inside the view.
     */
    /*package*/ final boolean pointInView(float localX, float localY) {
        return localX >= 0 && localX < (mRight - mLeft)
                && localY >= 0 && localY < (mBottom - mTop);
    }



6.多點觸控:

   這裏主要說一下MotionEventAction是怎麼存儲多點信息的

       mAction的低8位(也就是0-7位)是動作類型信息。

       mAction8-15位呢,是觸控點的索引信息。(即表示是哪一個觸控點的事件)。

 這是一個高效的存儲方式,不但記錄的事件類型,還說明了是哪個觸摸點的事件。

所以我們可以看到MotionEvent有兩個方法,一個是getAction(),另一個是getActionMasked(),實際上第二個方法就是通過掩碼的方式隱去觸控點的索引信息,只記錄事件類型

/**
     * Bit mask of the parts of the action code that are the action itself.
     */
    public static final int ACTION_MASK             = 0xff;
public final int getActionMasked() {
        return mAction & ACTION_MASK;
}

我們再來看一下如何得索引值呢?這也是一個利用位運算的高效寫法,可以學習一下

Public final int getActionIndex() {
    return  (mAction & 0xff00) >> 8;
}

好了,以上就是事件在java層的傳遞過程,下面我們分析c++

 

二.C++層事件處理

      在之前java層的事件傳遞流程中,我們看到最開始的時候是InputEventReceiveronInputEvent方法被回調,那這個方法是被誰調用的呢?帶着這個疑問我們進入C++層探尋。

 

這是InputEventReceiver初始化流程,包括java和c層,我們看到最後Looper監聽一個文件描述符,(代碼位於frameworks/base/core/jni/android_view_InputEventReceiver.cpp):

 int fd = mInputConsumer.getChannel()->getFd();
  mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);

這裏是epoll機制(關於Looper中的epool原理,可參考Looper中的epool機制),至於這個監聽的fd是何方神聖現在還不知道,但是addFd的第四個參數就是監聽到事件之後的回調,果然在NativeInputEventReceiver中實現了handleEvent方法,查看該方法實現:

1.NativeInputEventReceiver::handleEvent(171)  status_t status = consumeEvents(env, false , -1, NULL);
2.NativeInputEventReceiver::consumeEvents(313)
   <span style="font-size:12px;">env->CallVoidMethod(receiverObj.get(),gInputEventReceiverClassInfo.dispatchInputEvent,seq, inputEventObj);</span>

  這裏就是native層調用java層代碼,即調用InputEventReceiver.java的dispatchInputEvent方法

    private void dispatchInputEvent(int seq, InputEvent event) {
        .......
        onInputEvent(event);//到這裏就是我們開始需要的入口點了,終於找到了 不容易
}

至此,我們終於知道onInputEvent是被誰一步步回調的了。

這裏我們先不追蹤剛纔那個被監聽的fd從哪裏來,先對以上過程做一個總結:

1.Activity初始化的過程中,ActivityThread會通過android.view.WindowManagerImpl類爲該Activity創建一個ViewRoot實例,並且會通過調用ViewRoot類的setView成員函數把與該Activity關聯的View設置到這個ViewRoot中去,而setView邏輯中會初始化一個InputEventReceiver作爲input事件的客戶端接受者,InputEventReceiver初始化的過程會在c++層初始化一個NativeInputEventReceiver對象,而在NativeInputEventReceiver初始化的時候,會通過傳遞來的Looper(客戶端主線程)監聽一個輸入事件的fd(epoll監聽機制),當輸入事件發生的時候會回調handleEvent方法,最終回調到InputEventReceiver的onInputEvent方法將事件傳遞給java層。

2.InputEventReceiver的onInputEvent方法被回調之後,會首先將事件傳給InputStage責任鏈,這裏面會判斷要不要傳遞給輸入法服務。如果只是motionEvent,就會首先傳遞給PhoneWindow的DecorView,DecorView會把這個事件傳給phonewindow的callback也就是當前activity,activity會首先把事件再次傳遞給phoneWindow,接着phoneWindow就讓事件在以DecorView爲根的viewGroup中層層傳遞,直到事件最終被view處理。當然如果我們重寫activity的dispatchTouchEvent方法,事件就到不了viewgroup中了。


 

三、輸入事件Fd究竟是何方神聖

     在上一步中,

int fd = mInputConsumer.getChannel()->getFd();
mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);

   我們知道這裏的fd是關鍵,爲什麼監聽這個fd就可以拿到輸入事件呢?爲了解決這個疑問,我們再次採用追本溯源的方式,一步步找到這個fd的源頭。經過追蹤,發現源頭還是在ViewRootImpl的setView方法中:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
.................................
mInputChannel = new InputChannel();
......................
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());
..........................
}


很明顯,在上一步中我們已經知道這是InputEventReceiver的初始化源頭,之後會進入InputEventReceiver的native層的初始化流程,並最終通過Looper監聽fd來最終監聽輸入事件。而這裏的mInputChannel很明顯就是int fd = mInputConsumer.getChannel()->getFd()中我們要找的InputChannel. ok,這裏這個InputChannel的源頭我們找到了,但是更大的疑問來了,明明這裏的mInputChannel只是一個簡單的初始化,並沒有監聽什麼端口之類的,爲什麼能在它哪裏監聽到輸入事件呢?只能是之後我們對mInputChannel又做了某些賦值處理,所以,我們繼續追蹤setView這個方法,果不其然發現了

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), 
mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);

這裏mWindowSession是WindowManagerService的遠程代理,最終實際上會調到WindowManagerService的addWindow方法中去,而在這個方法裏就有對mInputChannel進行真正賦值的操作,下面我們來看addWindow方法的邏輯:

 

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) {
                ................
1                String name = win.makeInputChannelName();
2                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
3                win.setInputChannel(inputChannels[0]);
4                inputChannels[1].transferTo(outInputChannel);
5                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
                ..............................
}

           

這裏簡單說明一下:
              第2行是創建一對InputChannel,我們大致可以想象一下,一個用於server端的寫,一個用於client端的讀
              第3行是server端賦值持有相關InputChannel,最終賦值給c++層的InputDispatcher中的InputPublisher                      
              作爲向client寫事件的入口socket
              第4行,將創建的InputChannel對中的一個賦值給client端的InputChannel
              第5行實際上最終是在InputDispatcher中通過Looper監聽client端返回的事件處理完成的信號,從而完成 
              整個事件處理鏈條。

           我們先來分析InputChannel.openInputChannelPair的過程:

                  最終調用到Jni層的android_view_InputChannel_nativeOpenInputChannelPair

static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
        jclass clazz, jstring nameObj) {
    const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
    String8 name(nameChars);
    env->ReleaseStringUTFChars(nameObj, nameChars);
    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
            new NativeInputChannel(serverChannel));
    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
            new NativeInputChannel(clientChannel));
    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
    return channelPair;
}


           這裏調用InputChannel::openInputChannelPair創建了server端和client端的通道,然後調用                           android_view_InputChannel_createInputChannel將native層InputChannel對象轉換爲Java層InputChannel對象,我們看一下InputChannel::openInputChannelPair的具體實現

 

status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        status_t result = -errno;
        ALOGE("channel '%s' ~ Could not create socket pair.  errno=%d",
                name.string(), errno);
        outServerChannel.clear();
        outClientChannel.clear();
        return result;
    }
 
    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
 
    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);
 
    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel= new InputChannel(clientChannelName, sockets[1]);
    return OK;
}


           這裏我們發現建立了一對匿名的已經連接的套接字socket,同時outServerChannel和outClientChannel分別持有

           這對套接字的兩端,這樣就實現了server端寫入輸入事件能被client端讀到。到這裏我們就明白了client端對

           輸入事件的監聽最終是監聽socket。

    

          再看另一個問題,就是之前WindowManagerService.addWindwo方法中:

            inputChannels[1].transferTo(outInputChannel);這實際上是將生成的outClientChannel賦值給outInputChannel,但這裏並沒有賦值給client端的InputChannel啊?這裏到底是怎麼影響到client端的InputChannel呢?

這裏實際上是利用binder調用中out關鍵字,我們來看一下IWindowSession.aidl中的addToDisplay方法聲明:

 int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,
            in int viewVisibility, in int layerStackId, out Rect outContentInsets,
            out Rect outStableInsets, out InputChannel outInputChannel);

  很明顯這裏使用了out關鍵字,對binder調用熟悉的同學就知道實際上這個關鍵字的作用就是我們傳遞給server端的參數在server進程中被改變的話會被反饋回給client端。這樣就相當於我們在client端可以拿到之前打開的那個socket,最終回到第二部分,就可以被Looper監聽這個socket了。          


這裏面大致上就把client端的InputChannel的賦值過程給介紹了。這裏面還有一個小細節值得介紹一下:

就是我們看到InputChannel.java中有這樣一個成員變量:

      @SuppressWarnings("unused")
      private long mPtr; // used by native code

 這裏實際上已經註釋的很清楚了,就是mPtr是用於java層InputChannel對象和native層的InputChannel對象轉換用的,我們來看一下android_view_InputChannel.cpp中將java對象轉換爲nativie對象的方法

static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env,
        jobject inputChannelObj) {
    jlong longPtr = env->GetLongField(inputChannelObj, gInputChannelClassInfo.mPtr);
    return reinterpret_cast<NativeInputChannel*>(longPtr);
}

           這裏我們可以看到實際上mPtr變量就是InputChannel對應的native對象的指針,只需要一個強制類型轉換就可以拿到NativeInputChannel*了,是很方便的。這種java變量和native變量的變換在整個android系統中是很普通的。

 

static void android_view_InputChannel_setNativeInputChannel(JNIEnv* env, jobject inputChannelObj,
        NativeInputChannel* nativeInputChannel) {
    env->SetLongField(inputChannelObj, gInputChannelClassInfo.mPtr,
             reinterpret_cast<jlong>(nativeInputChannel));
}

四、server端對輸入事件的監聽

     在第三部分分析中,我們知道會創建一對持有連接好的socket的InputChannel分別交給client端和server端,server端對socket寫入事件時,會被client端監聽到從而將輸入事件傳遞給上層IME輸入法或View、Activity.那麼現在問題來了,server端是如何獲取到輸入事件的呢,又是在哪裏往這個socket裏寫入事件的呢?這就來到了input子系統最複雜的一塊了,InputManagerService的初始化過程、以及對輸入事件的輪詢和分發過程。這裏面我們分兩部分講解:

1.InputManagerService的初始化過程

   

      這是InputManagerService的初始化流程,最終InputManager中會初始化兩個thread,InputReaderThread和DispatcherThread。這兩個thread會loop起來,其中InputReaderThread會wait在EventHub->getEvents這裏等待下面上報輸入事件;而DispatcherThread會mLooper->pollOnce(),睡眠在這裏,等待被InputReaderThread喚醒mLooper->wake().

EventHub是輸入設備的控制中心,它直接與input driver打交道。負責處理輸入設備的增減,查詢,輸入事件的處理並向上層提供getEvents()接口接收事件。在它的構造函數中,主要做三件事:
1. 創建epoll對象,之後就可以把各輸入設備的fd掛在上面多路等待輸入事件。
2. 建立用於喚醒的pipe,把讀端掛到epoll上,以後如果有設備參數的變化需要處理,而getEvents()又阻塞在設備上,就可以調用wake()在pipe的寫端寫入,就可以讓線程從等待中返回。
3. 利用inotify機制監聽/dev/input目錄下的變更,如有則意味着設備的變化,需要處理。

 

2.Server端對輸入事件的輪詢和分發過程

  

首先通過EventHub的getEvents函數讀取輸入設備事件,經過處理後從事件中獲得對應的deviceId,根據deviceId在mDevices數組中找到對應的輸入設備對象或者構造新的InputDevice對象,然後調用InputDevice的process函數。

在InputDevice的process函數中對每個事件由InputDevice對象的mMappers數組中登記的每個InputMapper對象依次處理,分別調用其process函數,對原始輸入事件進行映射。

 不同的事件類型採用了不同的具體InputMapper對象進行映射,如按鍵事件對應的是KeyboadInputMapper對象,另外還有TouchInputMapper、CursorInputMapper、VibratorInputMapper、SwitchInputMapper、JoystickInputMapper等映射對象,這些對象的類都是InputMapper虛擬類的具體類。

  一個支持多種類型的事件輸入的輸入設備,需要使用多個不同的輸入映射對象分別進行映射。

 在InputMapper對象的process函數中把事件封裝成NotifyArgs對象,然後調用InputMapper的監聽對象QueedInputListener的相關事件監聽接口函數,如notifyKey函數。

在QueedInputListener的監聽接口函數中作爲參數傳進來的NotifyArgs對象放入QueedInputListener的事件隊列ArgsQueue中,然後返回並在loopOnce函數中調用QueuedListener對象的flush函數。在flush函數中依次調用ArgsQueue隊列中由事件封裝成的NotifyArgs對象的notify函數,notify函數的參數也是一個監聽對象,在QueuedInputListener對象實例化時賦值,對應的是一個InputDispatcher對象。這裏面的QueuedInputListener是在InputReader初始化時賦值的,同樣InputDispatcher作爲QueuedInputListener的構造參數也是這時候傳進來的。NotifyArgs對象的notify函數調用其參數引用對象(InputDispatcher)的事件通知函數,以NotifyArgs對象爲參數,如按鍵事件對應的notifyKey。 

InputDispatcher對象的事件通知函數根據作爲參數傳進來的事件構造一個EventEntry對象,然後調用enqueueInboundEventLocked函數把構造的EventEntry事件對象放入內部事件隊列中(mInboundQueue),最後調用Looper的wake函數,喚醒事件提交線程即InputDispatcherThread線程來進行事件向管道中的提交。 

InputReader讀取的輸入事件作爲參數通過InputListenerInterface的接口函數被InputDispatcher接收。接收的事件構造爲EventEntry對象放入InputDispatcher對象的EventEntry類型的隊列mInboundQueue中,並喚醒InputDispatcherThread線程。InputDispatcherThread線程不斷從該隊列中讀取輸入事件。首先調用InputDispatcher對象的dispatchOnce函數;dispatchOnce函數又調用dispatchOnceInnerLocked函數,在dispatchOnceInnerLocked函數中進行一系列判斷後若是按鍵事件則調用dispatchKeyLocked函數;在dispatchKeyLocked函數中使用findFocusedWindowTargetsLocked函數尋找焦點窗口,並把找到的焦點窗口通過調用函數addWindowTargetLocked放入mCurrentInputTargets數組中,然後調用addMonitoringTargetsLocked爲剛纔找到的焦點窗口綁定一個inputChannel通道,接着調用dispatchEventLocked函數;在dispatchEventLocked函數中首先根據剛纔焦點窗口綁定的inputChannel找到對應的一個Connection對象,然後調用prepareDispatchCycleLocked;在prepareDispatchCycleLocked函數中調用enqueueDispatchEntryLocked函數,在enqueueDispatchEntryLocked函數中根據傳進來的事件對應的對象eventEntry構造一個 DispatchEntry對象,DispatchEntry對象中包含inputTarget的信息,並放入connection對象outboundQueue隊列;接着又調用startDispatchCycleLocked;

 在startDispatchCycleLocked函數中通過connection對象中的inputPublisher對象引用調用inputPublisher對象的publishKeyEvent函數或publishMotionEvent函數向客戶端發送事件,最終通過InputChannel中socket寫給client處理,這就解決了第三部分提出的問題。

 

anr流程

int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
        const EventEntry* entry,
        const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t* nextWakeupTime, const char* reason) {
.................................
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
mInputTargetWaitTimeoutTime = currentTime + timeout;
}
.......................
if (currentTime >= mInputTargetWaitTimeoutTime) {
        onANRLocked(currentTime, applicationHandle, windowHandle,
                entry->eventTime, mInputTargetWaitStartTime, reason);

        // Force poll loop to wake up immediately on next iteration once we get the
        // ANR response back from the policy.
        *nextWakeupTime = LONG_LONG_MIN;
        return INPUT_EVENT_INJECTION_PENDING;
    } else {
        // Force poll loop to wake up when timeout is due.
        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
            *nextWakeupTime = mInputTargetWaitTimeoutTime;
        }
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

 

按鍵事件的攔截

 按鍵事件的攔截請求包括隊列前的事件攔截(譬如keycode_power)和提交前的攔截(譬如keycode_home),下面以按鍵事件的攔截爲例進行說明。

 對於隊列前的按鍵事件攔截請求,在InputDispatche對象的notifyKey函數和injectInputEvent函數提交輸入按鍵事件到隊列之前,通過調用InputDispatcherPolicyInterface接口對象的interceptKeyBeforeQueueing函數發送事件的隊列之前攔截請求;而對於事件提交前的按鍵事件攔截通過調用InputDispatcherPolicyInterface接口對象的interceptKeyBeforeDispatching回調函數來發送其攔截請求,其流程如下:

 在InputDispatche對象的事件提交函數dispatchKeyLocked中判斷事件的policyFlags是否含有POLICY_FLAG_PASS_TO_USER,如果含有POLICY_FLAG_PASS_TO_USER就調用InputDispatche對象的postCommandLocked函數,postCommandLocked的參數爲doInterceptKeyBeforeDispatchingLockedInterruptible函數指針。在postCommandLocked函數中構造一個CommandEntry對象,其command方法爲參數傳進來的函數指針,並放入mCommandQueue隊列,然後函數dispatchKeyLocked返回,事件在事件提交之前被攔截不再繼續派發。

        在函數dispatchKeyLocked返回到dispatchOnce,繼續調用runCommandsLockedInterruptible函數對mCommandQueue隊列中的命令對象進行處理,對於mCommandQueue隊列中的每一個命令對象執行命令對象的command方法,對於剛纔放入的命令實際調用的是作爲postCommandLocke參數的InputDispatch:doInterceptKeyBeforeDispatchingLockedInterruptible函數指針;因此doInterceptKeyBeforeDispatchingLockedInterrup

tible函數被調用,在doInterceptKeyBeforeDispatchingLockedInterruptible函數中調用InputDispatcherPolicyInterface接口對象的interceptKeyBeforeDispatching事件攔截回調函數發送事件提交前的攔截請求。

 InputDispatcherPolicyInterface接口對象在InputDispatcher對象構造時把對NativeInputManager對象的引用作爲參數傳入賦值給InputDispatcher對象的InputDispatcherPolicyInterface接口類型的字段mPolicy,因此上述的對InputDispatcherPolicyInterface接口事件的調用實際調用的是NativeInputManager對象的相應函數。

 而NativeInputManage對象的攔截和過濾回調函數又通過JNI調用JAVA層InputManagerService服務的相應函數,如interceptKeyBeforeDispatching、interceptKeyBeforeQueueing和filterInputEvent函數。

 InputManagerService服務的事件攔截函數實際調用的是WindowManagerCallbacks接口的相應接口函數,而InputMonitor對象是WindowManagerCallbacks接口的實現,因此對WindowManagerCallbacks接口函數的調用就是對InputMonitor對象的相應函數的調用。InputMonitor對象的對應函數又通過WindowManagerService窗口管理服務中的PhoneWindowManager對象引用調用PhoneWindowManager對象的對應函數,最終完成事件的攔截處理。

在client處理完事件之後也是要通知server端的,這也是通過Looper::addFd實現監聽的

 

好了,以上就是整個分析過程。最後我們給出input子系統整個clinet端和server端的關係圖

 

發佈了37 篇原創文章 · 獲贊 8 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章