Android Input framework(一)

 

1 InputFramework概述

Android輸入系統在整個圖形系統框架中扮演了很重要的角色,主要負責用戶消息的管理,具體職責包括以下幾個方面:

 

1、 從底層驅動中獲取各種原始的用戶消息,包括按鍵、觸摸屏、鼠標、滾跡球等用戶事件消息。

2、 最原始消息進行預處理,包括兩個方面:一方面,將消息轉化成系統可以處理的消息事件;另一方面,處理一些特殊的事件,比如HOMEMENUPOWER鍵等處理。

3、 將處理後的消息事件分發到各個應用進程,這個需要使用IPC機制,Android系統使用管道來進行消息的傳遞。

 

Android系統使用InputManager類來管理消息,而具體的功能則是通過InputReaderThreadInputDispatcherThread兩個線程來實現。其中InputReaderThread線程負責消息的讀取,而

InputDispatcherThread則負責消息的預處理和分發到各個應用進程中。輸入系統的整體框架如下圖所示:

Android <wbr>Input <wbr>Framework(一)

從框圖中可以看出,Android輸入系統通過EventHub收集輸入設備的原始數據,InputReader調用接口讀取EventHub中獲取的數據,然後通知InputDispatcher數據已經準備好,InputDispatcher獲得數據回調InputManager的接口間接回調WMS 中的InputMonitor對輸入消息進行處理,如果WMS沒有消耗掉該消息,則InputDispatcher會將該消息通過管道的方式,直接發送到應用進程中,當前焦點應用的ViewrootImpl會收到該消息,並對消息進行分發處理,最終將其發送到對應的View對象中進行界面響應。

 

1 NativeInputManager初始化

WindowManagerService構造函數中,經過JNI調用完成了NativeInputManager的初始化,初始化工作有如下幾點。

1.1 調用時序圖

Android <wbr>Input <wbr>Framework(一)

1.2 類圖對象關係

Android <wbr>Input <wbr>Framework(一)

1.3Native層註冊java層的CallBacks回調接口

InputManagerService類中定義了一個Callback接口:

  publicinterface Callbacks {

       public void notifyConfigurationChanged();

       public void notifyLidSwitchChanged(long whenNanos, booleanlidOpen);

       public void notifyInputChannelBroken(InputWindowHandleinputWindowHandle);

       public long notifyANR(InputApplicationHandleinputApplicationHandle,

               InputWindowHandle inputWindowHandle);

       public int interceptKeyBeforeQueueing(KeyEvent event, intpolicyFlags, boolean isScreenOn);

       public int interceptMotionBeforeQueueingWhenScreenOff(intpolicyFlags);

       public long interceptKeyBeforeDispatching(InputWindowHandlefocus,

               KeyEvent event, int policyFlags);

       public KeyEvent dispatchUnhandledKey(InputWindowHandlefocus,

               KeyEventevent, int policyFlags);

       public int getPointerLayer();

   }

InputMonitor實現了InputManagerService.Callbacks接口,在WindowManagerService的構造函數中創建了InputMonitor對象,並以mInputMonitor作爲參數創建InputManagerService的對象,在InputManagerService構造函數中,將mInputMonitor作爲參數調用了JNI函數nativeInit(),將回調接口傳到JNI層,在需要的時候,JNI在回調mInputMonitor中的函數,實現數據才傳遞。

class InputMonitorimplements InputManagerService.Callbacks{

   ……

}

privateWindowManagerService(Context context, PowerManagerServicepm,

           boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore){

final InputMonitormInputMonitor = new InputMonitor(this);

mInputManager = newInputManagerService(context, mInputMonitor);

}

 

1.4 創建InputDispatcherInputReader線程

Android <wbr>Input <wbr>Framework(一)

InputManagerService的構造函數中,調用了

Frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp中的nativeInit()方法。

首先在JNI方法中創建了一個NativeInputManager對象,該對象內部構造函數中又創建了一個InputManager對象。注意這裏的InputManagernative的。關鍵在於InputManager的構造函數中,創建了兩個非常重要的對象,InputDispatcherInputReader,前者是作爲消息派發者,後者是input消息的讀取者。然後在initialize()方法中,將前面創建的兩對象作爲參數,創建了對應的兩個線程,分別是InputReaderTheadInputDispatchThread

 

消息傳送

2.1 創建InputChannel

     前面也簡單說過,InputDispatch和客戶端之間是通過Pipe傳遞消息的,Pipelinux系統調用的一部分,我們需要關注的是Pipe所包含的讀寫描述符,而爲了程序設計的便利,Android增加了一個InputChannel類,有兩個作用,一個是保存消息端口對應的Pipe的讀寫描述符,另一個是通過使用InputChannel所提供的函數創建底層的Pipe對象,。Pipe爲管道的意思。

2.1.1 時序圖(創建和在wms中註冊)

Android <wbr>Input <wbr>Framework(一)

2.1.2 創建InputChannel流程

    在上面時序圖看出,創建InputChannel是從添加窗口開始的,當客戶需要添加窗口的時候,會創建ViewRootImpl對象,並調用它的setView()方法,通過IPC通信調用SessionaddWindow()方法,其中就包含了一個InputChannel對象,裏面沒有數據的空殼,然後調用到WMSaddWindow()方法,希望Wms創建真正的InputChannel

WmsaddWindow()

  public int addWindow(Session session, IWindowclient, int seq,

           WindowManager.LayoutParams attrs, int viewVisibility,

           Rect outContentInsets, InputChannel outInputChannel) {

           ……

if (outInputChannel != null&& (attrs.inputFeatures

                   &WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0){

               String name = win.makeInputChannelName();

               InputChannel[]inputChannels =InputChannel.openInputChannelPair(name);

               win.setInputChannel(inputChannels[0]);

               inputChannels[1].transferTo(outInputChannel);

               

               mInputManager.registerInputChannel(win.mInputChannel,win.mInputWindowHandle);

           }

          ……

}

addWindow()內調用了InputChannel.openInputChannelPaire()方法開始創建InputChannel,過程如下圖:

Android <wbr>Input <wbr>Framework(一)

1)       InputChannel.java的靜態方法openInputChannelPai()中,調用了JNI 

frameworks/base/coar/jni/android_Inputview_Channel.cpp

 android_view_InputChannel_nativeOpenInputChannelPair()方法

static jobjectArrayandroid_view_InputChannel_nativeOpenInputChannelPair(JNIEnv*env,

       jclass clazz, jstring nameObj) {

   ……

   sp<InputChannel>serverChannel;

   sp<InputChannel>clientChannel;

   status_t result = InputChannel::openInputChannelPair(name,serverChannel, clientChannel);

   ……

   jobjectArray channelPair = env->NewObjectArray(2,gInputChannelClassInfo.clazz, NULL);

    if(env->ExceptionCheck()) {

       return NULL;

   }

 

   jobject serverChannelObj =android_view_InputChannel_createInputChannel(env,

           new NativeInputChannel(serverChannel));

    if(env->ExceptionCheck()) {

       return NULL;

   }

 

   jobject clientChannelObj =android_view_InputChannel_createInputChannel(env,

           new NativeInputChannel(clientChannel));

    if(env->ExceptionCheck()) {

       return NULL;

   }

 

   env->SetObjectArrayElement(channelPair, 0,serverChannelObj);

   env->SetObjectArrayElement(channelPair, 1,clientChannelObj);

    returnchannelPair;

}

在上面代碼中,新建了Native層兩個通道變量,分別是serverChannelclientChannel,然後將兩個空殼傳入InputChannel::openInputChannelPair()中。

2)       NativeInputChannel類是定義在InputTrasport.cpp文件中,openInputChannelPair()方法纔是真正的創建管道,然後賦值給兩個空殼。

   

status_tInputChannel::openInputChannelPair(const String8&name,

       sp<InputChannel>&outServerChannel,sp<InputChannel>&outClientChannel) {

    intsockets[2];

    if(socketpair(AF_UNIX, SOCK_SEQPACKET,0, sockets)) {

       status_t result = -errno;

       ALOGE("channel '%s' ~ Could not create socketpair.  errno=%d",

               name.string(), errno);

       outServerChannel.clear();

       outClientChannel.clear();

       return result;

   }

 

    intbufferSize = 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 = newInputChannel(serverChannelName, sockets[0]);

 

   String8 clientChannelName = name;

   clientChannelName.append(" (client)");

   outClientChannel = newInputChannel(clientChannelName, sockets[1]);

   return OK;

}

調用Linuxsocketpair()方法建立一對匿名的已經連接的套接字,然後調用setsockopt()方法爲其分配內存,然後用其做爲參數,創建native環境中的InputChannel對象,分別賦值給outServerChannel、和outClientChannel指針,

3)       創建好兩個native層的InputChannel對象後,存在channelPair數組中,然後通過JNI返回到JAVA環境中。

JNI

env->SetObjectArrayElement(channelPair, 0,serverChannelObj);

   env->SetObjectArrayElement(channelPair, 1,clientChannelObj);

    returnchannelPair;

 

JAVA

InputChannel[] inputChannels= InputChannel.openInputChannelPair(name);

 

2.2 WMS中註冊InputChannel

在上面一節介紹了創建InputChannel的過程,其實的創建了兩個InputChannel對象,一個作爲服務端,需要Wms對其進行註冊,另一個作爲客戶端,需要客戶進程對其進行註冊。這一節我們看Wms中註冊InputChannel。在2.1.1的時序圖中我們可以看到,在WmsaddWindow()方法中,調用InputChannel.openInputChannelPair(name);返回一對InputChannel對象,裏面包含了管道的描述符等信息,然後調用InputManagerService.registerInputChannel進行註冊,即時序圖中第八步開始,。流程圖如下:

Android <wbr>Input <wbr>Framework(一)

通過上面可以看到,經過InputManagerService的調用,最終到達JNInativeRegisterInputChannel方法中,首先通過參數獲取到Native層的InputChannel對象,還記得在Native InputManager初始化中,在JNI創建了一個NativeInputManager對象。這裏就獲取到該對象,然後調用NativeInputManager::registerInputChannel()開始註冊:

status_tNativeInputManager::registerInputChannel(JNIEnv* env,

       constsp<InputChannel>&inputChannel,

       constsp<InputWindowHandle>&inputWindowHandle, bool monitor) {

    returnmInputManager->getDispatcher()->registerInputChannel(

           inputChannel, inputWindowHandle, monitor);

}

NativeInputManager對象中有一個InputManager對象引用,InputManager對象中又有一個InputDispatcher對象的引用,所以將調用到InputDispatcher::registerInputChannel()進行註冊:

status_tInputDispatcher::registerInputChannel(constsp<InputChannel>&inputChannel,

       constsp<InputWindowHandle>&inputWindowHandle, bool monitor) {

       ……

       sp<Connection>connection = new Connection(inputChannel, inputWindowHandle,monitor);

       int fd = inputChannel->getFd();

       mConnectionsByFd.add(fd,connection);

       if (monitor) {

           mMonitoringChannels.push(inputChannel);

       }

       ……

       mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT,handleReceiveCallback, this);

    } //release lock

   return OK;

}

首先需要創建一個Connection對象,即客戶端與服務端連接的接口,然後將該對象加入到InputDispatchermConnectionsByFd列表中,當需要通過管道發送消息的時候,從該列表中取出Connection對象,該對象與客戶端是向對應的,之後調用mLooper->addFd()方法,把InputChannel對應的描述符添加到mLooper內部的描述符列表中。這裏完成了wmsInputChannel的註冊,即serverChannel

2.3客戶進程註冊InputChannel

Wms中創建了一對InputChannel,其中serverChannel被註冊到了InputDispatcher線程中,另一個clientChannel則需要註冊到客戶進程中,從而使得客戶進程可以直接接收到InputDispatcher發送的用戶消息。

客戶端註冊InputChannel和在InputManager中註冊InputChannel的本質是相同的,即告訴所在進程的native looper對象,讓它監控指定的文件描述符即可。客戶端的InputChannel來源於調用WmsaddWindow()時,最後一個參數是一個InputChannel類型的輸出參數。下面看調用的時序圖

2.3.1 時序圖

Android <wbr>Input <wbr>Framework(一)

2.3.2 註冊流程

   2.3.1時序圖中,我們看到setView()方法,該方法是在客戶創建新的窗口時候調用,前面我們也看到了,在這個方法中通過IPC通信調用了Wms.addWindow(),創建了一對管道InputChannel,並在Wms中註冊了serverChannel,完成了這兩步之後,那麼就需要對客戶進程註冊clientChannel了。

1)       客戶進程是創建的新的進程,那麼爲改進程創建一個用於接收消息的WindowInputEventReceiver對象,它繼承於InputEventReceiver類。在InputEventReceive構造函數中,調用了JNI方法nativeInit()

static jintnativeInit(JNIEnv* env, jclass clazz, jobjectreceiverObj,

       jobject inputChannelObj, jobject messageQueueObj) {

   sp<InputChannel> inputChannel =android_view_InputChannel_getInputChannel(env,

           inputChannelObj);

   ……

   sp<NativeInputEventReceiver> receiver= newNativeInputEventReceiver(env,

           receiverObj, inputChannel, messageQueue);

   status_t status =receiver->initialize();

……

 returnreinterpret_cast<jint>(receiver.get());

}

 

2)       調用android_view_InputChannel_getInputChannel獲取native層的InputChannel對象,參數創建一個NativeInputEventReceiver對象,把InputChannel作爲該對象的內部參數。

3)       initialize()方法中,把文件描述符添加到內部的接收描述符列表中,使得客戶進程窗口可以接收到發往該文件描述符的消息

status_tNativeInputEventReceiver::initialize() {

    intreceiveFd =mInputConsumer.getChannel()->getFd();

   mMessageQueue->getLooper()->addFd(receiveFd,0, ALOOPER_EVENT_INPUT, this, NULL);

   return OK;

}

2.4 Wms中獲取Input消息

用戶消息可以分爲兩類,一個是Key消息,另一個是Motion消息。對於Motion消息,InputDispatcher是通過pipe直接把消息發往客戶窗口的,Wms不能對這些消息進行任何的前置處理,而對於Key消息,則會先回調Wms中的Key消息處理函數,在Wms中不處理的消息,纔會把消息發往客戶端。一般情況下,wms中僅僅處理一些系統的Key消息,比如”Home”鍵、音量鍵等。

2.4.1 時序圖(Wms獲取Key消息)

Android <wbr>Input <wbr>Framework(一)

2.4.2 流程分析

   InputDispatcher中,收到InputReader發送過來的Event消息,最終調用到InputDispatcher::dispatchKeyLocked()方法,開始派發按鍵消息,接着消息傳到NativeInputManager中。在第一節NativeInputManager初始化中,我們向JNI註冊了java層的一些回調接口,這時候就用到了的。

nsecs_tNativeInputManager::interceptKeyBeforeDispatching(

       constsp<InputWindowHandle>&inputWindowHandle,

       const KeyEvent* keyEvent, uint32_t policyFlags) {

       ……

       if (keyEventObj) {

           jlong delayMillis = env->CallLongMethod(mServiceObj,

                   gServiceClassInfo.interceptKeyBeforeDispatching,

                   inputWindowHandleObj, keyEventObj, policyFlags);

       ……

   return result;

}

調用JNIenv->CallLongMethod()方法,回調JAVA層方法,mServiceObj對應的是java層的InputManagerService類的實例,gServiceClassInfo.interceptKeyBeforeDispatching指的是InputManagerServiceinterceptKeyBeforeDispatching()函數,於是消息就從JNI傳遞到了JAVA層。最終消息就傳到了PhoneWindowManager中。

2.5客戶窗口獲取Input消息

2.5.1時序圖(Key消息分析)

  Android <wbr>Input <wbr>Framework(一)


2.5.2 流程分析

無論是Key消息還是Motion消息,都是通過Pipe管道傳遞到客戶進程窗口的。所有的客戶進程都有一個主線程,即ActivityThread類,該類每次開始的時候就會進入一個Looper循環中,然後就不斷的從MessageQueue中讀取消息,如果沒有消息,則進入wait狀態,直到下一個消息。

InputDispatcher中,獲取了InputReaderKey消息,經過一步步處理,調用到startDispatchCycleLocked()方法:

voidInputDispatcher::startDispatchCycleLocked(nsecs_tcurrentTime,

       const sp<Connection>&connection) {

      ……

      case EventEntry::TYPE_KEY: {

           KeyEntry* keyEntry =static_cast<KeyEntry*>(eventEntry);

 

           // Publish the key event.

           status = connection->inputPublisher.publishKeyEvent(……);

           break;

       }

       ……

}

注意到這裏的connection,這是Wms註冊InputChannel的時候創建的Connection對象,然後將KeyEvent消息寫入管道中。

status_tInputPublisher::publishKeyEvent(

   ….

   returnmChannel->sendMessage(&msg);

}

 

status_tInputChannel::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);

…….

   return OK;

}

 

消息寫入管道後,經過MessageQueue通知到客戶進程,然後在ViewRootImpl中,調用doConsumeBatchedInput()開始讀取keyEvent消息。看看receiveMessage():

status_tInputChannel::receiveMessage(InputMessage* msg) {

   ssize_t nRead;

    do{

       nRead =::recv(mFd, msg, sizeof(InputMessage),MSG_DONTWAIT);

    }while (nRead == -1 && errno ==EINTR);

   ……

   return OK;

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章