之前在分析InputDispatcher分发的时候,知道输入事件最终从Native层传到了framework上层,到达了ViewRootImpl通过setView方法注册的WindowInputEventReceiver的onInputEvent方法。
接下来分析输入事件是如何在App层传递的。
篇幅限制,将该笔记拆分为3部分:
@Override
public void onInputEvent(InputEvent event) {
......
List<InputEvent> processedEvents;
try {
processedEvents =
mInputCompatProcessor.processInputEventForCompatibility(event);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (processedEvents != null) {
......
} else {
enqueueInputEvent(event, this, 0, true);
}
}
这里的InputEventCompatProcessor.processInputEventCompatibility方法主要是为Android M平台以下的Motion事件处理做一些兼容处理,对于Android M以上的平台直接返回null,那么后续直接调用ViewRootImpl.enqueueInputEvent。
@UnsupportedAppUsage
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
......
// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
......
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
1)、通过ViewRootImpl.obtainQueuedInputEvent创建一个QueuedInputEvent对象,将QueuedInputEvent成员变量mEvent指向当前处理的输入事件。
2)、将上一步得到的QueuedInputEvent入队。类QueuedInputEvent实现了一个等待处理的输入事件队列,ViewRootImpl的成员变量mPendingInputEventHead指向这个待处理队列的队首,mPendingInputEventTail指向队尾,QueuedInputEvent的成员变量mNext指向排在当前等待处理的事件之后的下一个事件QueuedInputEvent。
如果此时队伍中没有正在排队的事件,那么mPendingInputEventHead和mPendingInputEventTail都指向这个QueuedInputEvent对象。
如果此时队伍中有正在排队的事件,那么将队尾mPendingInputEventTail的成员变量mNext指向这个QueuedInputEvent对象,然后这个QueuedInputEvent对象变为队尾mPendingInputEventTail。
3)、在第1节调用ViewRootImpl.enqueueInputEvent的时候传入的processImmediately为true,那么调用doProcessInputEvents跳过调度直接处理。
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
mPendingInputEventCount -= 1;
......
deliverInputEvent(q);
}
// We are done processing all input events that we can process right now
// so we can clear the pending flag immediately.
if (mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = false;
mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
}
}
1)、遍历待处理队列,对每一个QueuedInputEvent对象调用ViewRootImpl.deliverInputEvent方法进行处理。
2)、遍历结束后,我们已经处理完了所有当前我们可以处理的输入事件,因此我们可以清除掉mProcessInputEventsScheduled这个待处理标记。这个标记只有在ViewRootImpl.scheduleProcessInputEvents方法中才会被置为true,而上一步我们是检测到参数processImmediately为true直接调用了当前的ViewRootImpl.doProcessInputEvents方法,没有走ViewRootImpl.scheduleProcessInputEvents。
重点分析对每一个QueuedInputEvent对象都调用的deliverInputEvent方法。
private void deliverInputEvent(QueuedInputEvent q) {
......
try {
if (mInputEventConsistencyVerifier != null) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "verifyEventConsistency");
try {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
......
if (stage != null) {
......
stage.deliver(q);
} else {
finishInputEvent(q);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
整个方法需要分成几部分来看。
if (mInputEventConsistencyVerifier != null) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "verifyEventConsistency");
try {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
InputEventConsistencyVerifier用来判断属于同一系列的输入事件的一致性,并且收集每一个检测出的错误,同时避免相同错误重复收集。
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
这里先不去分析InputStage是用来干啥的,先看下这里创建的局部变量stage最终指向了哪个InputStage。
这里QuquedInputEvent的两个方法都检测了当前QueuedInputEvent的flag:
public boolean shouldSkipIme() {
if ((mFlags & FLAG_DELIVER_POST_IME) != 0) {
return true;
}
return mEvent instanceof MotionEvent
&& (mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
|| mEvent.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER));
}
public boolean shouldSendToSynthesizer() {
if ((mFlags & FLAG_UNHANDLED) != 0) {
return true;
}
return false;
}
回顾在ViewRootImpl.enqueueInputEvent方法中创建QueuedInputEvent的时候,传入的lfags参数是0,因此这里对于所有flag的判断都会返回false。那么ViewRootImpl.shouldSendToSynthesizer方法返回false,而ViewRootImpl.shouldSkipIme方法会进一步判断当前是否是MotionEvent以及MotionEvent的输入源。
InputDevice在InputReader调用processEventsLocked函数处理从EventHub读取的原始数据的时候也遇到过,InputDevice描述了一个特定的输入设备的功能。
SOURCE_CLASS_POINTER代表了事件类型是触摸点,这些事件的输入源包括触摸屏、鼠标和手写笔:
/**
* The input source is a touch screen pointing device.
*
* @see #SOURCE_CLASS_POINTER
*/
public static final int SOURCE_TOUCHSCREEN = 0x00001000 | SOURCE_CLASS_POINTER;
/**
* The input source is a mouse pointing device.
* This code is also used for other mouse-like pointing devices such as trackpads
* and trackpoints.
*
* @see #SOURCE_CLASS_POINTER
*/
public static final int SOURCE_MOUSE = 0x00002000 | SOURCE_CLASS_POINTER;
/**
* The input source is a stylus pointing device.
*
* Note that this bit merely indicates that an input device is capable of obtaining
* input from a stylus. To determine whether a given touch event was produced
* by a stylus, examine the tool type returned by {@link MotionEvent#getToolType(int)}
* for each individual pointer.
*
* A single touch event may multiple pointers with different tool types,
* such as an event that has one pointer with tool type
* {@link MotionEvent#TOOL_TYPE_FINGER} and another pointer with tool type
* {@link MotionEvent#TOOL_TYPE_STYLUS}. So it is important to examine
* the tool type of each pointer, regardless of the source reported
* by {@link MotionEvent#getSource()}.
*
*
* @see #SOURCE_CLASS_POINTER
*/
public static final int SOURCE_STYLUS = 0x00004000 | SOURCE_CLASS_POINTER;
SOURCE_ROTARY_ENCODER代表了旋转编码设备,遇到的比较少:
/**
* The input source is a rotating encoder device whose motions should be interpreted as akin to
* those of a scroll wheel.
*
* @see #SOURCE_CLASS_NONE
*/
public static final int SOURCE_ROTARY_ENCODER = 0x00400000 | SOURCE_CLASS_NONE;
那么总结一下,如果当前输入事件是Motion类型且输入源是触摸点相关类型,或者输入源是旋转解码器类型,那么第一个InputStage选择mFirstPostImeInputStage,否则选择mFirstInputStage。
if (stage != null) {
......
stage.deliver(q);
} else {
finishInputEvent(q);
}
根据上一小节的分析,那么这里的stage可能是mFirstPostImeInputStage或mFirstInputStage。
再进一步分析前,需要看一下InputStage是干嘛的。
/**
* Base class for implementing a stage in the chain of responsibility
* for processing input events.
*
* Events are delivered to the stage by the {@link #deliver} method. The stage
* then has the choice of finishing the event or forwarding it to the next stage.
*
*/
abstract class InputStage {
private final InputStage mNext;
protected static final int FORWARD = 0;
protected static final int FINISH_HANDLED = 1;
protected static final int FINISH_NOT_HANDLED = 2;
private String mTracePrefix;
/**
* Creates an input stage.
* @param next The next stage to which events should be forwarded.
*/
public InputStage(InputStage next) {
mNext = next;
}
/**
* Delivers an event to be processed.
*/
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
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);
}
apply(q, result);
}
}
/**
* Marks the the input event as finished then forwards it to the next stage.
*/
protected void finish(QueuedInputEvent q, boolean handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
if (handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
}
forward(q);
}
/**
* Forwards the event to the next stage.
*/
protected void forward(QueuedInputEvent q) {
onDeliverToNext(q);
}
/**
* Applies a result code from {@link #onProcess} to the specified event.
*/
protected void apply(QueuedInputEvent q, int result) {
if (result == FORWARD) {
forward(q);
} else if (result == FINISH_HANDLED) {
finish(q, true);
} else if (result == FINISH_NOT_HANDLED) {
finish(q, false);
} else {
throw new IllegalArgumentException("Invalid result: " + result);
}
}
/**
* Called when an event is ready to be processed.
* @return A result code indicating how the event was handled.
*/
protected int onProcess(QueuedInputEvent q) {
return FORWARD;
}
/**
* Called when an event is being delivered to the next stage.
*/
protected void onDeliverToNext(QueuedInputEvent q) {
if (DEBUG_INPUT_STAGES) {
Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
}
if (mNext != null) {
mNext.deliver(q);
} else {
finishInputEvent(q);
}
}
protected void onWindowFocusChanged(boolean hasWindowFocus) {
if (mNext != null) {
mNext.onWindowFocusChanged(hasWindowFocus);
}
}
......
}
InputStage是用于实现处理输入事件的责任链中的一个阶段的基类。事件通过InputStage.deliver方法发送给stage,接下来stage有权决定结束掉这个事件或者把它转交给下一个stage。
根据InputStage的提供的方法接口,可以总结出输入事件在InputStage链中传递的一般规律:
1)、上一个InputStage调用InputStage.onDeliverToNext -> InputStage.deliver将输入事件发送到当前InputStage。
2.1)、如果输入事件被标记了QueuedInputEvent.FLAG_FINISHED,那么调用InputStage.forward继续将事件向下一个InputStage分发,当前InputStage不做处理。
2.2)、如果输入事件经过InputStage.shouldDropInputEvent判断应该被丢弃,那么调用InputStage.finish为输入事件添加QueuedInputEvent.FLAG_FINISHED标记,InputStage.finish中又调用InputStage.forward把事件分发给下一个InputStage。
2.3)、如果2.1和2.2的判断条件不满足,说明本次事件需要当前InputStage进行处理,那么调用InputStage.onProcess对事件进行处理,这是每一个InputStage处理事件的核心部分。
3)、根据InputStage.onProcess的处理结果,调用InputStage.apply方法判断是将事件在当前InputStage结束掉,还是调用InputStage.forward继续将事件分发给下一个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) {
mNext = next;
}
......
}
首先能看到InputStage有一个InputStage类型的mNext成员变量,在InputStage创建的时候传入,指向当前InputStage的下一个InputStage,以此构成一个链表形式。
所有InputStage创建的地方在ViewRootImpl.setView中,也就是在从system_server进程返回客户端InputChannel之后。
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
......
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
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;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
}
}
因此InputStage责任链的处理顺序是:
根据4.2的分析可知,MotionEvent是直接从EarlyPostImeInputStage这一阶段开始分发的。
从名字可以清晰的将InputStage分为三个阶段:IME处理前阶段,IME处理阶段,IME处理后阶段。
对这7个InputStage进行简介。
/**
* Delivers pre-ime input events to a native activity.
* Does not support pointer events.
*/
final class NativePreImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {
......
}
在IME处理之前,将输入事件发送给一个native Activity。不支持点触事件。
在此阶段只支持将KeyEvent类型的时间提前发送给native Activity,而且是异步进行的,也就是说,这里对输入事件的处理可能会推迟。
/**
* Delivers pre-ime input events to the view hierarchy.
* Does not support pointer events.
*/
final class ViewPreImeInputStage extends InputStage {
......
}
在IME处理之间,将输入事件发送给View层级结构。不支持点触事件。
主要是针对KeyEvent类型的事件,在将KeyEvent发送给IME并由IME消费掉之前,使用View.dispatchKeyEventPreIme和View.onKeyPreIme尝试进行一次拦截,主要是为了一些特殊情况,比如当BACK按键事件分发的时候,我们更希望View层级结构能够处理这个BACK按键事件来更新App的UI,而不是让IME接收到BACK键然后关闭掉IME窗口。
/**
* Delivers input events to the ime.
* Does not support pointer events.
*/
final class ImeInputStage extends AsyncInputStage
implements InputMethodManager.FinishedInputEventCallback {
......
}
将输入事件分发给输入法。不支持点触事件。
调用InputMethodManager.dispatchInputEvent对输入事件进行分发,之前处理过功能机按键的相关问题,比如向编辑框中输入信息的时候,数字按键事件KEYCODE_0、KEYCODE_1等会在这个阶段分发给InputMethodManager处理,不会发送给后续的InputStage。。
/**
* Performs early processing of post-ime input events.
*/
final class EarlyPostImeInputStage extends InputStage {
......
}
在IME处理之后,对输入事件进行早期处理。
主要是为了在输入事件进一步发送之前,判断当前输入事件是否会影响touch mode的进入和退出,以及,让AudioManager能够提前接收到输入事件等。
/**
* Delivers post-ime input events to a native activity.
*/
final class NativePostImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {
......
}
在IME处理阶段之后,将输入事件发送给native Activity。
这一步和4.3.2.1中NativePreImeInputStage的处理很像,不同的地方在于,NativePreImeInputStage只允许发送KeyEvent事件,而到了NativePostImeInputStage这里就不再限制输入事件的类型。在此阶段,native Activity仍然能比Java层的Activity进一步处理输入事件。
/**
* Delivers post-ime input events to the view hierarchy.
*/
final class ViewPostImeInputStage extends InputStage {
......
}
在IME处理阶段之后,将输入事件发送给View层级结构。
此阶段是输入事件发送给Activity和View的地方,后面重点分析。
/**
* Performs synthesis of new input events from unhandled input events.
*/
final class SyntheticInputStage extends InputStage {
......
}
从未处理的输入事件中合成新的输入事件。
此阶段是责任链的最后一个阶段,主要用来处理前几个阶段无法处理的输入事件类型,如轨迹球,游戏摇杆,导航面板等。
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
根据输入事件类型进行相应处理。
我们的重点分析处理处理点触事件类型的processPointerEvent和KeyEvent类型事件的processKeyEvent方法。