1 InputFramework概述
Android輸入系統在整個圖形系統框架中扮演了很重要的角色,主要負責用戶消息的管理,具體職責包括以下幾個方面:
1、 從底層驅動中獲取各種原始的用戶消息,包括按鍵、觸摸屏、鼠標、滾跡球等用戶事件消息。
2、 最原始消息進行預處理,包括兩個方面:一方面,將消息轉化成系統可以處理的消息事件;另一方面,處理一些特殊的事件,比如HOME、MENU、POWER鍵等處理。
3、 將處理後的消息事件分發到各個應用進程,這個需要使用IPC機制,Android系統使用管道來進行消息的傳遞。
Android系統使用InputManager類來管理消息,而具體的功能則是通過InputReaderThread和InputDispatcherThread兩個線程來實現。其中InputReaderThread線程負責消息的讀取,而
InputDispatcherThread則負責消息的預處理和分發到各個應用進程中。輸入系統的整體框架如下圖所示:
從框圖中可以看出,Android輸入系統通過EventHub收集輸入設備的原始數據,InputReader調用接口讀取EventHub中獲取的數據,然後通知InputDispatcher數據已經準備好,InputDispatcher獲得數據回調InputManager的接口間接回調WMS 中的InputMonitor對輸入消息進行處理,如果WMS沒有消耗掉該消息,則InputDispatcher會將該消息通過管道的方式,直接發送到應用進程中,當前焦點應用的ViewrootImpl會收到該消息,並對消息進行分發處理,最終將其發送到對應的View對象中進行界面響應。
1 NativeInputManager初始化
在WindowManagerService構造函數中,經過JNI調用完成了Native層InputManager的初始化,初始化工作有如下幾點。
1.1 調用時序圖
1.2 類圖對象關係
1.3在Native層註冊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 創建InputDispatcher和InputReader線程
在InputManagerService的構造函數中,調用了
Frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp中的nativeInit()方法。
首先在JNI方法中創建了一個NativeInputManager對象,該對象內部構造函數中又創建了一個InputManager對象。注意這裏的InputManager是native的。關鍵在於InputManager的構造函數中,創建了兩個非常重要的對象,InputDispatcher和InputReader,前者是作爲消息派發者,後者是input消息的讀取者。然後在initialize()方法中,將前面創建的兩對象作爲參數,創建了對應的兩個線程,分別是InputReaderThead和InputDispatchThread。
2 消息傳送
2.1 創建InputChannel
前面也簡單說過,InputDispatch和客戶端之間是通過Pipe傳遞消息的,Pipe是linux系統調用的一部分,我們需要關注的是Pipe所包含的讀寫描述符,而爲了程序設計的便利,Android增加了一個InputChannel類,有兩個作用,一個是保存消息端口對應的Pipe的讀寫描述符,另一個是通過使用InputChannel所提供的函數創建底層的Pipe對象,。Pipe爲管道的意思。
2.1.1 時序圖(創建和在wms中註冊)
2.1.2 創建InputChannel流程
在上面時序圖看出,創建InputChannel是從添加窗口開始的,當客戶需要添加窗口的時候,會創建ViewRootImpl對象,並調用它的setView()方法,通過IPC通信調用Session的addWindow()方法,其中就包含了一個InputChannel對象,裏面沒有數據的空殼,然後調用到WMS的addWindow()方法,希望Wms創建真正的InputChannel。
Wms的addWindow():
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,過程如下圖:
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層兩個通道變量,分別是serverChannel和clientChannel,然後將兩個空殼傳入InputChannel::openInputChannelPair()中。
2) Native的InputChannel類是定義在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; } |
調用Linux的socketpair()方法建立一對匿名的已經連接的套接字,然後調用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的時序圖中我們可以看到,在Wms的addWindow()方法中,調用InputChannel.openInputChannelPair(name);返回一對InputChannel對象,裏面包含了管道的描述符等信息,然後調用InputManagerService.registerInputChannel進行註冊,即時序圖中第八步開始,。流程圖如下:
通過上面可以看到,經過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對象,即客戶端與服務端連接的接口,然後將該對象加入到InputDispatcher的mConnectionsByFd列表中,當需要通過管道發送消息的時候,從該列表中取出Connection對象,該對象與客戶端是向對應的,之後調用mLooper->addFd()方法,把InputChannel對應的描述符添加到mLooper內部的描述符列表中。這裏完成了wms的InputChannel的註冊,即serverChannel。
2.3客戶進程註冊InputChannel
Wms中創建了一對InputChannel,其中serverChannel被註冊到了InputDispatcher線程中,另一個clientChannel則需要註冊到客戶進程中,從而使得客戶進程可以直接接收到InputDispatcher發送的用戶消息。
客戶端註冊InputChannel和在InputManager中註冊InputChannel的本質是相同的,即告訴所在進程的native looper對象,讓它監控指定的文件描述符即可。客戶端的InputChannel來源於調用Wms的addWindow()時,最後一個參數是一個InputChannel類型的輸出參數。下面看調用的時序圖
2.3.1 時序圖
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消息)
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; } |
調用JNI的env->CallLongMethod()方法,回調JAVA層方法,mServiceObj對應的是java層的InputManagerService類的實例,gServiceClassInfo.interceptKeyBeforeDispatching指的是InputManagerService的interceptKeyBeforeDispatching()函數,於是消息就從JNI傳遞到了JAVA層。最終消息就傳到了PhoneWindowManager中。
2.5客戶窗口獲取Input消息
2.5.1時序圖(以Key消息分析)
2.5.2 流程分析
無論是Key消息還是Motion消息,都是通過Pipe管道傳遞到客戶進程窗口的。所有的客戶進程都有一個主線程,即ActivityThread類,該類每次開始的時候就會進入一個Looper循環中,然後就不斷的從MessageQueue中讀取消息,如果沒有消息,則進入wait狀態,直到下一個消息。
在InputDispatcher中,獲取了InputReader的Key消息,經過一步步處理,調用到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; } |