深入解析Android中的事件傳遞
前言
前段時(shí)間工作中遇到了一個(gè)問(wèn)題,即在軟鍵盤(pán)彈出后想監(jiān)聽(tīng)back事件,但是在Activity中重寫(xiě)了對(duì)應(yīng)的onKeyDown函數(shù)卻怎么也監(jiān)聽(tīng)不到,經(jīng)過(guò)一陣Google之后才發(fā)現(xiàn)需要重寫(xiě)View的dispatchKeyEventPreIme函數(shù)才行。當(dāng)時(shí)就覺(jué)得這個(gè)函數(shù)名字很熟悉,仔細(xì)思索一番以后才恍然大悟,當(dāng)初看WMS源碼的時(shí)候有過(guò)這方面的了解,現(xiàn)在卻把它忘到了九霄云外,于是決定寫(xiě)這篇文章,權(quán)當(dāng)記錄。
InputManagerService
首先我們知道,不論是“鍵盤(pán)事件”還是“點(diǎn)擊事件”,都是系統(tǒng)底層傳給我們的,當(dāng)然這里最底層的Linux Kernel我們不去討論,我們的起點(diǎn)從Framework層開(kāi)始。有看過(guò)Android Framework層源碼的同學(xué)已經(jīng)比較清楚,其中存在非常多的XXXManagerService,它們運(yùn)行在system_server進(jìn)程中,著名的如AMS(ActivityManagerService)和WMS(WindowManagerService)等等。這里和Android事件相關(guān)的Service就是InputManagerService,那么就先讓我們看看它是如何進(jìn)行工作的吧。
public void start() {
Slog.i(TAG, "Starting input manager");
nativeStart(mPtr);
........
}
看到這個(gè)nativeStart,是不是倒吸一口涼氣,沒(méi)錯(cuò),是一個(gè)native方法。不過(guò)這也沒(méi)辦法,畢竟底層嘛,少不了和c打交道~
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
status_t result = im->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
可以看到,調(diào)用了InputManager的start方法。
status_t InputManager::start() {
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputDispatcher thread due to error %d.", result);
return result;
}
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputReader thread due to error %d.", result);
mDispatcherThread->requestExit();
return result;
}
return OK;
}
其中初始化了兩個(gè)線(xiàn)程——ReaderThread和DispatcherThread。這兩個(gè)線(xiàn)程的作用非常重要,前者接受來(lái)自設(shè)備的事件并且將其封裝成上層看得懂的信息,后者負(fù)責(zé)把事件分發(fā)出去??梢哉f(shuō),我們上層的Activity或者是View的事件,都是來(lái)自于這兩個(gè)線(xiàn)程。這里我不展開(kāi)講了,有興趣的同學(xué)可以自行根據(jù)源碼進(jìn)行分析。有趣的是,DispatcherThread在輪詢(xún)點(diǎn)擊事件的過(guò)程中,采用的Looper的形式,可見(jiàn)Android中的源碼真的是處處相關(guān)聯(lián),所以不要覺(jué)得某一部分的源碼看了沒(méi)用,說(shuō)不定以后你就會(huì)用到了。
ViewRootImpl
從前一小節(jié)我們得知,設(shè)備的點(diǎn)擊事件是通過(guò)InputManagerService來(lái)進(jìn)行傳遞的,其中存在兩個(gè)線(xiàn)程一個(gè)用于處理,一個(gè)用于分發(fā),那么事件分發(fā)到哪里去呢?直接發(fā)到Activity或者View中嗎?這顯然是不合理的,所以Framework層中存在一個(gè)ViewRootImpl類(lèi),作為兩者溝通的橋梁。需要注意的是,該類(lèi)在老版本的源碼中名為ViewRoot。
ViewRootImpl這個(gè)類(lèi)是在Activity的resume生命周期中初始化的,調(diào)用了ViewRootImpl.setView函數(shù),下面讓我們看看這個(gè)函數(shù)做了什么。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
..........
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//Attention here!!!
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
............
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
}
}
}
這個(gè)方法非常的長(zhǎng),我先截取了一小段,看我標(biāo)注的[Attention here],創(chuàng)建了一個(gè)InputChannel的實(shí)例,并且通過(guò)mWindowSession.addToDisplay方法將其添加到了mWindowSession中。
final IWindowSession mWindowSession;
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
......
}
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
mWindowSession是什么呢?通過(guò)上面的代碼我們可以知道,mWindowSession就是WindowManagerService中的一個(gè)內(nèi)部實(shí)例。getWindowManagerService拿到的事WindowManagerNative的proxy對(duì)象,所以由此我們可以知道,mWindowSession也是用來(lái)IPC的。
如果大家對(duì)上面一段話(huà)不是很了解,換句話(huà)說(shuō)不了解Android的Binder機(jī)制的話(huà),可以先去自行了結(jié)一下。
回到上面的setView函數(shù),mWindowSession.addToDisplay方法肯定調(diào)用的是對(duì)應(yīng)remote的addToDisplay方法,其中會(huì)調(diào)用WindowManagerService::addWindow方法去將InputChannel注冊(cè)到WMS中。
看到這里大家可能會(huì)有疑問(wèn),第一小節(jié)說(shuō)的是InputManagerService管理設(shè)備的事件,怎么到了這一小節(jié)就變成了和WindowManagerService打交道呢?秘密其實(shí)就在mWindowSession.addToDisplay方法中。
WindowManagerService
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
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方法中,創(chuàng)建了一個(gè)InputChannel的數(shù)組,數(shù)組中有兩個(gè)InputChannel,第一個(gè)是remote端的,通過(guò) mInputManager.registerInputChannel方法講其注冊(cè)到InputManager中;第二個(gè)是native端的,通過(guò)inputChannels[1].transferTo(outInputChannel)方法,將其指向outInputChannel,而outInputChannel就是前面setView傳過(guò)來(lái)的那個(gè)InputChannel,也就是ViewRootImpl里的。
通過(guò)這一段代碼,我們知道,當(dāng)Activity初始化的時(shí)候,我們就會(huì)在WMS中注冊(cè)兩個(gè)InputChannel,remote端的InputChannel注冊(cè)到InputManager中,用于接受ReaderThread和DispatcherThread傳遞過(guò)來(lái)的信息,native端的InputChannel指向ViewRootImpl中的InputChannel,用于接受remote端的InputChannel傳遞過(guò)來(lái)的信息。
最后,回到ViewRootImpl的setView方法的最后,有這么一句:
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
WindowInputEventReceiver,就是我們最終接受事件的接收器了。
鍵盤(pán)事件的傳遞
下面讓我們看看WindowInputEventReceiver做了什么。
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
@Override
public void onBatchedInputEventPending() {
if (mUnbufferedInputDispatch) {
super.onBatchedInputEventPending();
} else {
scheduleConsumeBatchedInput();
}
}
@Override
public void dispose() {
unscheduleConsumeBatchedInput();
super.dispose();
}
}
很簡(jiǎn)單,回調(diào)到onInputEvent函數(shù)的時(shí)候,就調(diào)用ViewRootImpl的enqueueInputEvent函數(shù)。
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
.........
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
可以看到,如果需要立即處理該事件,就直接調(diào)用doProcessInputEvents函數(shù),否則調(diào)用scheduleProcessInputEvents函數(shù)加入調(diào)度。
這里我們一切從簡(jiǎn),直接看doProcessInputEvents函數(shù)。
void doProcessInputEvents() {
........
deliverInputEvent(q);
........
}
private void deliverInputEvent(QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (stage != null) {
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
可以看到deliverInputEvent函數(shù)中,存在一個(gè)很有意思的東西叫InputStage,通過(guò)一些標(biāo)記位去確定到底是用哪個(gè)InputStage去處理。
這些InputStage是在哪里初始化的呢?顯示是在setView函數(shù)啦。
mSyntheticInputStage = new SyntheticInputStage(); InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage); InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, "aq:native-post-ime:" + counterSuffix); InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); InputStage imeStage = new ImeInputStage(earlyPostImeStage, "aq:ime:" + counterSuffix); InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage); InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, "aq:native-pre-ime:" + counterSuffix); mFirstInputStage = nativePreImeStage; mFirstPostImeInputStage = earlyPostImeStage;
可以看到初始化了如此多的InputStage。這些stage的調(diào)用順序是嚴(yán)格控制的,Ime的意思是輸入法,所以大家應(yīng)該了解這些preIme和postIme是什么意思了吧?
從上面得知,最終會(huì)調(diào)用InputStage的deliver函數(shù):
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
apply(q, onProcess(q));
}
}
其apply方法被各個(gè)子類(lèi)重寫(xiě)的,下面我們以ViewPreImeInputStage為例:
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
}
return FORWARD;
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mView.dispatchKeyEventPreIme(event)) {
return FINISH_HANDLED;
}
return FORWARD;
}
可以看到,會(huì)調(diào)用View的dispatchKeyEventPreIme方法。看到這里,文章最開(kāi)頭的那個(gè)問(wèn)題也就迎刃而解了,為什么在輸入法彈出的情況下,監(jiān)聽(tīng)Activity的onKeyDown沒(méi)有用呢?因?yàn)樵撌录惠斎敕ㄏ牧?,?duì)應(yīng)的,就是說(shuō)走到了imeStage這個(gè)InputStage中;那為什么重寫(xiě)View的dispatchKeyEventPreIme方法就可以呢?因?yàn)樗窃赩iewPreImeInputStage中被調(diào)用的,還沒(méi)有輪到imeStage呢~
總結(jié)
通過(guò)這樣的一篇分析,相信大家對(duì)Android中的事件分發(fā)已經(jīng)有了一定的了解,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
android使用NotificationListenerService監(jiān)聽(tīng)通知欄消息
本篇文章主要介紹了android使用NotificationListenerService監(jiān)聽(tīng)通知欄消息,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-01-01
android實(shí)現(xiàn)快遞跟蹤進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)快遞跟蹤進(jìn)度條,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
Android Bitmap詳解及Bitmap的內(nèi)存優(yōu)化
這篇文章主要介紹了Android Bitmap詳解及Bitmap的內(nèi)存優(yōu)化的相關(guān)資料,Bitmap是Android系統(tǒng)中的圖像處理的最重要類(lèi)之一。用它可以獲取圖像文件信息,進(jìn)行圖像剪切、旋轉(zhuǎn)、縮放等操作,并可以指定格式保存圖像文件,需要的朋友可以參考下2017-03-03
Android檢測(cè)手機(jī)多點(diǎn)觸摸點(diǎn)數(shù)的方法
這篇文章主要為大家詳細(xì)介紹了Android檢測(cè)手機(jī)多點(diǎn)觸摸點(diǎn)數(shù)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
ANDROID中使用VIEWFLIPPER類(lèi)實(shí)現(xiàn)屏幕切換(關(guān)于坐標(biāo)軸的問(wèn)題已補(bǔ)充更改)
本篇文章主要介紹了ANDROID中使用VIEWFLIPPER類(lèi)實(shí)現(xiàn)屏幕切換,具有一定的參考價(jià)值,有興趣的可以了解一下。2016-11-11
Android 自動(dòng)判斷是電話(huà),網(wǎng)址,EMAIL方法之Linkify的使用
本篇文章小編為大家介紹,在Android中 自動(dòng)判斷是電話(huà),網(wǎng)址,EMAIL方法之Linkify的使用。需要的朋友參考下2013-04-04
Flutter實(shí)現(xiàn)滑動(dòng)塊驗(yàn)證碼功能
這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)滑動(dòng)塊驗(yàn)證碼功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

