Input子系統是一個龐大的系統,爲了簡單明瞭的介紹該系統,我們採用分模塊以及倒序追蹤的方式來一步步解開它的面紗。
一、java層事件傳遞過程
我們從這個button被點擊來研究input子系統中事件的傳遞。
廢話不多說,對button加一個OnTouchListener,在其onTouch方法上加一個斷點,直接利用eclipse的debug工具查看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.InputEventReceiver的onInputEvent方法被回調,開啓了java層touch事件傳遞,至於InputEventReceiver是被誰回調的,這裏不得而知,我們稍後再分析。
2.InputStage. NativePreImeInputStage、ViewPreImeInputStage、ImeInputStage、EarlyPostImeInputStage、NativePostImeInputStage、ViewPostImeInputStage、SyntheticInputStage構成一個輸入事件責任處理鏈,如果本階段對事件沒有處理,則傳遞到下一個對象進行處理,直至事件被處理。NativePreImeInputStage、ViewPreImeInputStage、ImeInputStage三個類用來實現輸入法的按鍵派發和處理,如果事件不傳遞到輸入法服務中,這三個類可以跳過,直接從EarlyPostImeInputStage對象開始處理,在ViewPostImeInputStage對象處理階段調用了主View 對象(對應PhoneWindow中的DecorView對象)的事件提交函數如(dispatchTouchEvent)函數向視圖對象提交輸入事件,在當前窗口的視圖樹中派發事件。
3.MainActivity. DecorView在傳遞事件過程中首先將事件傳給Activity的dispatchTouchEvent()方法,所以我們可以重寫這個方法來阻止事件繼續傳遞,在手錶項目中爲了屏蔽應用左滑退出的功能,就採用這種方式做到的。
4.ViewGroup. 這是touch事件在層層viewgroup中傳遞的過程,如果某個viewgroup攔截了事件,譬如手錶中交互是左滑退出,這裏面實際上就是SwipeDismissLayout檢測到左滑手勢時回調OnDismissedListener的onDismissed()方法,最終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返回true,viewgroup就會攔截touch事件,由自己的onTouchEvent方法處理,並且之後的事件不經過onInterceptTouchEvent,直接由onTouchEvent處理
ii. action_down事件,如果子view的onTouchEvent返回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.多點觸控:
這裏主要說一下MotionEvent的Action是怎麼存儲多點信息的
mAction的低8位(也就是0-7位)是動作類型信息。
mAction的8-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層的事件傳遞流程中,我們看到最開始的時候是InputEventReceiver的onInputEvent方法被回調,那這個方法是被誰調用的呢?帶着這個疑問我們進入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端的關係圖