努比亞技術團隊原創(chuàng)內(nèi)容,轉(zhuǎn)載請務必注明出處。
- 傳遞流程
- Java層事件傳遞流程
- 傳遞過程
- 詳細傳遞過程
- Native傳遞事件到Java
- InputEventReceiver分發(fā)事件
- Java層事件入口
- 對事件進行兼容性處理
- 轉(zhuǎn)變事件類型并加入隊列
- 循環(huán)事件鏈并進行分發(fā)
- 將事件分發(fā)到InputStage
- InputStage
- 分發(fā)傳遞事件
- 處理input事件
- 處理key事件
- DecorView處理事件
- Window.Callback(Activity)分發(fā)事件
- PhoneWindow中處理事件
- DecorView繼續(xù)分發(fā)事件到View樹
- 小結
安卓中輸入事件主要分為KeyEvent和MotionEvent兩種,本篇我主要介紹輸入事件是如何產(chǎn)生,然后又通過什么方式向用戶層傳遞,過程中都發(fā)生了什么,文中所列出的代碼均是基于Android 11的源碼進行介紹。考慮到大家對上層事件的傳遞比較熟悉,所以,此篇我采用從上至下倒序方式來介紹,主要介紹事件從native層到Java視圖之間的傳遞流程。
傳遞流程
在介紹之前先看一下安卓輸入事件傳遞的大致流程,本篇我會基于下圖中所列出的路線從上至下的方式進行介紹:
這里首先eventhub構建的時候會遍歷整個/dev/input路徑下的fd,并將其添加到epoll中,同時還會監(jiān)聽此路徑下新的設備的創(chuàng)建和卸載。當driver向特定描述符寫入事件后,會觸發(fā)喚醒epoll起來工作,這時候eventHub通過read方法從描述符中讀取原始事件,然后通過簡單封裝成rawEvent并傳遞給InputReader。
InputReader中的threadLoop中會調(diào)用eventHub的getEvents來獲取輸入事件,然后通過調(diào)用notifyxxx方法將事件傳遞到InputDispater并最終通過notifyxxx方法傳遞到上層。
Java層事件傳遞流程
Java層主要介紹事件從Native層如果向上層傳遞,并且事件是如何到達Activity、Window以及ViewGroup的整個樹上的。
傳遞過程
先來看一下Java層整個過程的調(diào)用時序圖:
詳細傳遞過程
Native傳遞事件到Java
在android_view_InputEventReceiver中的consumeEvents方法中會從InputConsumer中獲取input事件,然后通過jni將事件向上層傳遞。
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64,
getInputChannelName().c_str(), toString(consumeBatches), frameTime);
}
// 省略若干行
// 這里通過InputConsumer的consume方法獲取到inputevent
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent,
&motionEventType, &touchMoveNum, &flag);
// 省略若干行
if (inputEventObj) {
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str());
}
// 此處通過jni調(diào)用InputEventReceiver的dispatchInputEvent方法進行事件的分發(fā),
// 從而將input事件傳遞到java層
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching input event.");
skipCallbacks = true;
}
env->DeleteLocalRef(inputEventObj);
} else {
ALOGW("channel '%s' ~ Failed to obtain event object.",
getInputChannelName().c_str());
skipCallbacks = true;
}
if (skipCallbacks) {
mInputConsumer.sendFinishedSignal(seq, false);
}
}
InputEventReceiver分發(fā)事件
在InputEventReceiver中的dispatchInputEvent方法中會調(diào)用onInputEvent方法進行事件的處理,InputEventReceiver是抽象類,它的一個子類WindowInputEventReceiver是用來處理input事件的,WindowInputEventReceiver是frameworks/base/core/java/android/view/ViewRootImpl.java的內(nèi)部類,它復寫了onInputEvent方法,該方法中會調(diào)用enqueueInputEvent方法對事件進行入隊。
Java層事件入口
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
// 直接調(diào)用onInputEvent
onInputEvent(event);
}
public void onInputEvent(InputEvent event) {
// 直接調(diào)用finishInputEvent回收事件,所以此方法需要子類復寫
finishInputEvent(event, false);
}
dispatchInputEvent方法是jni直接調(diào)用的方法,屬于Java層的入口方法,其中會直接調(diào)用onInputEvent方法,onInputEvent方法中直接調(diào)用了finishInputEvent回收事件,所以就需要子類去實現(xiàn)具體的分發(fā)邏輯。
對事件進行兼容性處理
onInputEvent方法中首先調(diào)用InputCompatProcessor的processInputEventForCompatibility方法對事件進行兼容性處理,這個方法中會判斷應用的targetSdkVersion如果小于M并且是motion事件則進行兼容處理并返回,否則返回null;然后會調(diào)用enqueueInputEvent方法對事件進行入隊。
@Override
public void onInputEvent(InputEvent event) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
List<InputEvent> processedEvents;
try {
// 這塊兒主要是對低版本進行兼容性處理
processedEvents =
mInputCompatProcessor.processInputEventForCompatibility(event);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (processedEvents != null) {
// 安卓M及以下版本,在這里處理
if (processedEvents.isEmpty()) {
// InputEvent consumed by mInputCompatProcessor
finishInputEvent(event, true);
} else {
for (int i = 0; i < processedEvents.size(); i++) {
enqueueInputEvent(
processedEvents.get(i), this,
QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
}
}
} else {
// 這里對事件進行入隊
enqueueInputEvent(event, this, 0, true);
}
}
public InputEventCompatProcessor(Context context) {
mContext = context;
// 獲取應用的targetsdk版本
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mProcessedEvents = new ArrayList<>();
}
public List<InputEvent> processInputEventForCompatibility(InputEvent e) {
// 小于M并且是motion事件則進行兼容處理
if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
mProcessedEvents.clear();
MotionEvent motion = (MotionEvent) e;
final int mask =
MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
final int buttonState = motion.getButtonState();
final int compatButtonState = (buttonState & mask) >> 4;
if (compatButtonState != 0) {
motion.setButtonState(buttonState | compatButtonState);
}
mProcessedEvents.add(motion);
return mProcessedEvents;
}
return null;
}
轉(zhuǎn)變事件類型并加入隊列
此方法主要是將InputEvent轉(zhuǎn)變?yōu)镼ueuedInputEvent并將其放到鏈表的末尾,然后調(diào)用doProcessInputEvents方法進行處理
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
// 轉(zhuǎn)變?yōu)镼ueuedInputEvent
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// 將事件插入到鏈表的末尾
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
if (processImmediately) {
// 調(diào)用doProcessInputEvents繼續(xù)處理
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
循環(huán)事件鏈并進行分發(fā)
此方法中會遍歷整個事件鏈表,對每個事件調(diào)用deliverInputEvent方法進行分發(fā)
void doProcessInputEvents() {
// 遍歷整個鏈表,對事件進行分發(fā)
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
// 省略若干行
// 分發(fā)事件
deliverInputEvent(q);
}
}
將事件分發(fā)到InputStage
此方法中會根據(jù)flag獲取InputStage,然后調(diào)用InputStage的deliver方法分發(fā)事件。這里的InputStage后面會介紹到,它主要是用來將事件的處理分成多個階段進行,詳細介紹見后文。
private void deliverInputEvent(QueuedInputEvent q) {
// 省略若干行
try {
// 省略若干行
InputStage stage;
if (q.shouldSendToSynthesizer()) {
// flag包含F(xiàn)LAG_UNHANDLED會走這里
stage = mSyntheticInputStage;
} else {
// 是否跳過輸入法窗口進行分發(fā)
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
// 省略若干行
if (stage != null) {
// 處理窗口焦點變更
handleWindowFocusChanged();
// 分發(fā)事件
stage.deliver(q);
} else {
finishInputEvent(q);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
InputStage
InputStage主要是用來將事件的處理分成若干個階段(stage)進行,事件依次經(jīng)過每一個stage,如果該事件沒有被處理(標識為FLAG_FINISHED),則該stage就會調(diào)用onProcess方法處理,然后調(diào)用forward執(zhí)行下一個stage的處理;如果該事件被標識為處理則直接調(diào)用forward,執(zhí)行下一個stage的處理,直到?jīng)]有下一個stage(也就是最后一個SyntheticInputStage)。這里一共有7中stage,各個stage間串聯(lián)起來,形成一個鏈表,各個stage的處理過程大致如下圖所示:
先來看下這些stage是如何串起來的,所有的Stage都繼承于InputStage,而InputStage是抽象類,它的定義如下:
abstract class InputStage {
private final InputStage mNext;
/**
* Creates an input stage.
* @param next The next stage to which events should be forwarded.
*/
public InputStage(InputStage next) {
// 從構成函數(shù)的定義能夠看到,傳入的next會賦值給當前實例的next,
// 因此,先插入的就會是最后一個節(jié)點(頭插法),最終會形成一個鏈表
mNext = next;
}
}
在ViewRootImpl的setView方法中有以下代碼段:
// 如下創(chuàng)建出來的7個實例會串在一起形成一個鏈表,
// 鏈表的頭是最后創(chuàng)建出來的nativePreImeStage,
// 鏈表的尾是首先構造出來的mSyntheticInputStage
// Set up the input pipeline.
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
// 輸入法對應的stage
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
// 第一個處理事件的stage為NativePreImeInputStage
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
分發(fā)傳遞事件
該方法中會通過flag判斷事件是否已經(jīng)處理,若已經(jīng)處理則向前調(diào)用下一個stage處理事件(next的deliver方法),否則會調(diào)用onProcess來處理事件(此方法需要子類實現(xiàn)),然后會根據(jù)處理的結果判斷是否需要調(diào)用鏈表中的下一個stage來繼續(xù)處理。
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
// 調(diào)用next的deliver方法繼續(xù)分發(fā)處理
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
traceEvent(q, Trace.TRACE_TAG_VIEW);
final int result;
try {
// 自身處理事件
result = onProcess(q);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// 判斷是否需要繼續(xù)分發(fā)處理
apply(q, result);
}
}
處理input事件
這里我們以ViewPostImeInputStage為例(此stage會將事件傳遞到視圖層)介紹事件的分發(fā)過程,在onProcess方法中根據(jù)event的類型調(diào)用不同的方法進行分發(fā)。
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
// 處理key事件
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
// 處理pointer事件
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
// 處理軌跡球事件
return processTrackballEvent(q);
} else {
// 處理一般的motion事件
return processGenericMotionEvent(q);
}
}
}
處理key事件
此方法中會調(diào)用view的dispatchKeyEvent方法來將input事件分發(fā)到view樹上,然后按照view的事件分發(fā)機制繼續(xù)分發(fā)處理事件,需要注意的是這里的mView是decorview實例,在ViewRootImpl的setView方法中被設置。
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// 省略若干行
// 調(diào)用view的dispatchKeyEvent來分發(fā)事件,此處的mView是decorview
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
// 省略若干行
// 繼續(xù)調(diào)用鏈表的下一個stage來處理
return FORWARD;
}
DecorView處理事件
mView在ViewRootImpl的setView方法中被賦值,被賦值的對象便是DecorView的實例,而在ViewPostImeInputStage的onPressess方法中會將key事件通過dispatchKeyEvent方法傳遞到DecorView中。
dispatchKeyEvent
此方法中先獲取Window.Callback對象,然后調(diào)用其dispatchKeyEvent繼續(xù)處理,若callback為null則調(diào)用父類同名方法來處理。最后,還會回調(diào)window的onKeyDown和onKeyUp方法,需要注意的是Activity和Dialog都默認實現(xiàn)了Window.Callback接口的方法,所以這里就會將事件傳遞到Activity或者Dialog里。
public boolean dispatchKeyEvent(KeyEvent event) {
// 省略若干行
if (!mWindow.isDestroyed()) {
// window沒有銷毀,如何存在Window.Callback,
// 則調(diào)用callback的dispatchKeyEvent繼續(xù)處理
// 否則調(diào)用父類的dispatchKeyEvent來處理
final Window.Callback cb = mWindow.getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
// 這里回調(diào)window的onKeyDown和onKeyUp方法
return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
Window.Callback(Activity)分發(fā)事件
這里以Activity來介紹事件的傳遞流程,查看Activity的dispatchKeyEvent方法。
dispatchKeyEvent
public boolean dispatchKeyEvent(KeyEvent event) {
// 省略若干行
// 調(diào)用Window的superDispatchKeyEvent方法繼續(xù)處理
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
// 此處調(diào)用KeyEvent的dispatch,傳入的receiver為當前實例,
// 其內(nèi)部會根據(jù)event的action來調(diào)用當前實例的onKeyDown和onKeyUp方法
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
PhoneWindow中處理事件
Activity中getWindow返回的其實是PhoneWindow的實例,看一下PhoneWindow的方法實現(xiàn)。
superDispatchKeyEvent
此方法中會直接調(diào)用mDecor的同名方法,這個mDecor是DecorView的實例。
public boolean superDispatchKeyEvent(KeyEvent event) {
// 調(diào)用DecorView的同名方法
return mDecor.superDispatchKeyEvent(event);
}
DecorView繼續(xù)分發(fā)事件到View樹
這里Activity通過調(diào)用DecorView的superDispatchKeyEvent方法,又將事件傳入到DecorView里了,這里和前面事件傳遞到DecorView的不同之處是前面?zhèn)鬟f到dispatchKeyEvent,而這里是superDispatchKeyEvent。
superDispatchKeyEvent
此方法中會直接調(diào)用DecorView的父類的dispatchKeyEvent方法,如果事件沒有被處理掉的話,就會通過ViewRootImpl將其作為UnhandledEvent進行處理。
public boolean superDispatchKeyEvent(KeyEvent event) {
// 省略若干行
// 這里直接調(diào)用父類的dispatchKeyEvent方法,DecorView的父類是FrameLayout,
// 所以就分發(fā)到ViewGroup了,接下來就按照View樹的形式從根View向子View分發(fā)
if (super.dispatchKeyEvent(event)) {
return true;
}
// 如果沒有被消費掉,則通過ViewRootImpl將其作為Unhandled繼續(xù)處理
return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
}
小結
通過以上分析,我們便了解了input事件從Native層傳遞到Java層,然后繼續(xù)傳遞到View樹上的整個過程。需要注意的是,在各個傳遞環(huán)節(jié),如果調(diào)用方法返回了true,就代表它消費掉了本次事件,那么就不會繼續(xù)朝后傳遞。另外,對于ViewGroup來說,存在onInterceptTouchEvent方法,此方法用來攔截事件向子view分發(fā),如果返回了true,事件就會傳遞到當前ViewGroup的onTouchEvent中而不在向子View傳遞;否則的話,會分發(fā)到當前ViewGroup,然后再傳遞到子View。由于View不能包含子View,所以View不存在攔截方法。