总结
本文从源码角度,描述了如下4个阶段:
- 从底层硬件产生的触摸事件,并将事件传递到
InputEventReceiver
中。 - 由
InputEventReceiver
沟通ViewRootImpl
,将事件计入ViewRootImpl
的事件队列中。 - 由
ViewRootImpl
通过多个Stage
职责对象构成职责链,来按序处理事件。 - 不同的
Stage
对象将触摸事件投递到不同的对象中(触摸板、导航栏、实体按键或视图)。
概述
这里的事件是指来源于硬件的事件,诸如:屏幕的按压、触摸(屏幕解锁),实体按键的按压(调整音量),甚至于实体按键的组合使用(截屏)。
事件分类
// 公共基础类输入事件。
// 提供了获取输入设备,修改事件来源,深拷贝,序列化,事件重用的功能
public abstract class InputEvent implements Parcelable {
}
// 用于反馈:键和按钮事件的对象。
public class KeyEvent extends InputEvent implements Parcelable {
}
// 用于报告运动(鼠标,笔,手指,轨迹球)事件的对象。
// 运动事件可以保持绝对或相对运动和其他数据,取决于设备的类型。
public final class MotionEvent extends InputEvent implements Parcelable {
}
依据事件的分类描述,则对屏幕的触摸、滑动事件都应由MotionEvent
处理。事实也是如此:
// android.view.View
public boolean dispatchTouchEvent(MotionEvent event) {
}
事件产生 —— 通过命令创造
以 MotionEvent
事件为例,先尝试分析 MotionEvent
对象是如何创建的。提到创建对象必然逃不开new
关键字,所以全文搜索下不难找到下面代码片段。
因为MotionEvent
存在大量的创建与释放,所以在这里构建了一个大小为MAX_RECYCLED (10)
的池,以便于复用。
static private MotionEvent obtain() {
final MotionEvent ev;
synchronized (gRecyclerLock) {
ev = gRecyclerTop;
if (ev == null) {
return new MotionEvent();
}
gRecyclerTop = ev.mNext;
gRecyclerUsed -= 1;
}
ev.mNext = null;
ev.prepareForReuse();
return ev;
}
/**
* 回收MotionEvent,以供稍后调用者重复使用。
* 调用此函数后,您不能再次触摸事件(因为其已被释放)。
*/
@Override
public final void recycle() {
super.recycle();
synchronized (gRecyclerLock) {
if (gRecyclerUsed < MAX_RECYCLED) {
gRecyclerUsed++;
mNext = gRecyclerTop;
gRecyclerTop = this;
}
}
}
如果你打开编辑器并且进入到 MotionEvent
类中,你会发现该类有多个obtain
方法(如下图)。�
考虑到 MotionEvent
的无参数obtain()
方法是私有的,并且其他的obtain(xxx)
方法都在内部引用了无参数obtain()
方法。所以外部一定需要调用obtain(xxx)
的方法,并且该参数一定不是MotionEvent
。
所以排除上图中的倒数两个方法,并对其它Android
推荐的方法(倒数4、5已不被推荐依次进行全文查找。
最终查找结果如下:
public class Input {
private void sendMove(int inputSource, float dx, float dy) {
long now = SystemClock.uptimeMillis();
injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, dx, dy, 0.0f);
}
}
private void injectMotionEvent(int inputSource, int action, long when, float x, float y, float pressure) {
final float DEFAULT_SIZE = 1.0f;
final int DEFAULT_META_STATE = 0;
final float DEFAULT_PRECISION_X = 1.0f;
final float DEFAULT_PRECISION_Y = 1.0f;
final int DEFAULT_DEVICE_ID = 0;
final int DEFAULT_EDGE_FLAGS = 0;
MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, DEFAULT_SIZE,
DEFAULT_META_STATE, DEFAULT_PRECISION_X, DEFAULT_PRECISION_Y, DEFAULT_DEVICE_ID,
DEFAULT_EDGE_FLAGS);
event.setSource(inputSource);
InputManager.getInstance().injectInputEvent(event,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
没什么好说的,继续跟进 InputManager
。
public final class InputManager {
private final IInputManager mIm;
public boolean injectInputEvent(InputEvent event, int mode) {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
if (mode != INJECT_INPUT_EVENT_MODE_ASYNC
&& mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
&& mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
throw new IllegalArgumentException("mode is invalid");
}
try {
return mIm.injectInputEvent(event, mode);
} catch (RemoteException ex) {
return false;
}
}
}
到底问题的关键变成了mIm
,mIm
的类型是IInputManager
。
public static InputManager getInstance() {
synchronized (InputManager.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.INPUT_SERVICE);
sInstance = new InputManager(IInputManager.Stub.asInterface(b));
}
return sInstance;
}
}
看来IInputManager
是InputManager
的远程代理对象,他们之间通过Binder
通讯。
事件产生 —— 通过屏幕触摸
底层硬件调用
你对设备的触摸事件,将通过底层方法进行调用。因为目前我对 Native 部分的知识比较匮乏,所以就不对具体调用过程展开分析了。此处就将底层会处理相关的事件,并生成MotionEvent
的对象并调用InputEventReceiver#dispatchInputEvent()
方法作为论据,为下一步的分析提供支撑。
InputEventReceiver
是事件的源头
public abstract class InputEventReceiver {
// 被 native 方法调用
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
}
ViewRootImpl
绝佳的中介
ViewRootImpl
绝佳的中介,它既联系了WMS、Window、PhoneWindow、View
这条线,同时也联系了InputEvent
。所以它具备将InputEvent
派发到指定的View
的能力。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// 始终按顺序将输入事件排队,而不管其时间戳。
// 我们这样做是因为应用程序或IME可以注入关键事件以响应触摸事件,
// 并且我们要确保注入的键按照它们被接收的顺序被处理,
// 并且我们不能相信注入的事件的时间戳是单调的。
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) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
InputEventReceiver receiver, int flags) {
QueuedInputEvent q = mQueuedInputEventPool;
if (q != null) {
mQueuedInputEventPoolSize -= 1;
mQueuedInputEventPool = q.mNext;
q.mNext = null;
} else {
q = new QueuedInputEvent();
}
q.mEvent = event;
q.mReceiver = receiver;
q.mFlags = flags;
return q;
}
}
将QueuedInputEvent
理解成封装了InputEvent
、InputEventReceiver
的封装类即可。
另外processImmediately
类为true
,那么enqueueInputEvent
会立即调用doProcessInputEvents
。否则会加入到消息队列中,然后按顺序处理。当然无论以何种方式,最终都会调用到doProcessInputEvents
。
输入事件的处理
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
private void deliverInputEvent(QueuedInputEvent q) {
// ... 省略无关紧要的代码
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (stage != null) {
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
}
看到事件分发时,InputStage
类作为基类它实现了职责链的模板,并且stage.deliver(q)
方法实现了启动了职责链的处理流程。
职责链番外篇 ——职责链模板之职责对象 “输入阶段”
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;
public InputStage(InputStage next) {
mNext = next;
}
// ... 省略无关代码
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));
}
}
protected void finish(QueuedInputEvent q, boolean handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
if (handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
}
forward(q);
}
protected void forward(QueuedInputEvent q) {
onDeliverToNext(q);
}
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);
}
}
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);
}
}
}
既然基类InputStage
提供了职责链的模板,也提供了一系列onProcess、forward、finish、apply
方法,其目的也不言而喻:提供子类进行扩展的便捷。
// ----------------- InputStage的子类 ----------------------------
// 将预先输入事件提供给视图层次结构。
final class ViewPreImeInputStage extends InputStage {}
// 执行事后输入事件的早期处理。
final class EarlyPostImeInputStage extends InputStage {}
// 将后期输入事件提供给视图层次结构。
final class ViewPostImeInputStage extends InputStage {}
// 从未处理的输入事件执行新输入事件的合成。
final class SyntheticInputStage extends InputStage {}
// 用于实现支持输入事件的异步和无序处理的输入流水线级的基类。
abstract class AsyncInputStage extends InputStage {}
// ----------------- AsyncInputStage的子类----------------------------
// 将预先输入事件提供给 NativeActivity。
final class NativePreImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {}
// 将预先输入事件提供给视图层次结构。
final class ImeInputStage extends AsyncInputStage
implements InputMethodManager.FinishedInputEventCallback {}
// 将事后输入事件提交到 NativeActivity
final class NativePostImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {}
职责链番外篇 ——职责链模板之拼装职责链并执行事件处理
在ViewRootImpl
中已经定义了很多Stage
职责,那么这些职责由是在什么时候被拼装成链式调用的呢?
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
InputStage mFirstInputStage;
InputStage mFirstPostImeInputStage;
InputStage mSyntheticInputStage;
// ... 省略无关的代码
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// ... 省略无关的代码
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;
}
}
最终构造成如下的职责链:mSyntheticInputStage --> viewPostImeStage --> nativePostImeStage --> earlyPostImeStage --> imeStage --> viewPreImeStage --> viewPreImeStage
Stage
对象的详解
SyntheticInputStage
综合性的事件处理阶段,该类主要轨迹球、操作杆、导航面板及未捕获的事件使用键盘进行处理。
final class SyntheticInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
q.mFlags |= QueuedInputEvent.FLAG_RESYNTHESIZED;
if (q.mEvent instanceof MotionEvent) {
final MotionEvent event = (MotionEvent) q.mEvent;
final int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
// 轨迹球
mTrackball.process(event);
return FINISH_HANDLED;
} else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
// 操作杆
mJoystick.process(event);
return FINISH_HANDLED;
} else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
== InputDevice.SOURCE_TOUCH_NAVIGATION) {
// 导航面板
mTouchNavigation.process(event);
return FINISH_HANDLED;
}
} else if ((q.mFlags & QueuedInputEvent.FLAG_UNHANDLED) != 0) {
// 未捕获的事件,交由键盘去处理
mKeyboard.process((KeyEvent) q.mEvent);
return FINISH_HANDLED;
}
// 继续转发事件
return FORWARD;
}
}
ViewPostImeInputStage
视图输入处理阶段,主要处理按键、轨迹球、手指触摸及一般性的运动事件,触摸事件的分发对象是View。
processPointerEvent
方法是对触摸事件的预处理,在View
执行拖拽时将会使用到预处理后的值。
final class ViewPostImeInputStage 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);
}
}
}
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
boolean handled = mView.dispatchPointerEvent(event);
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
}
所以经由processPointerEvent
分析可知,你对屏幕上的某个按钮的点击事件,将总是先调用按钮的dispatchTouchEvent
的方法。
先提出上面这句论断,但也别忽视上面论断中的几个待确认的疑点:
-
mView
是什么(是根视图?还是焦点触发的视图)? -
dispatchPointerEvent
是如何派发事件的?
这些疑点我们另开一篇文章进行描述,这里先不打断原有的流程,我们接着继续分析。
NativePostImeInputStage
本地方法处理阶段,则构建可延迟的重用队列,此时执行操作将会异步回调结果。
final class NativePostImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {
@Override
protected int onProcess(QueuedInputEvent q) {
if (mInputQueue != null) {
mInputQueue.sendInputEvent(q.mEvent, q, false, this);
return DEFER;
}
return FORWARD;
}
}
EarlyPostImeInputStage
输入法早期处理阶段。
final class EarlyPostImeInputStage 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);
}
}
return FORWARD;
}
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent) q.mEvent;
// Translate the pointer event for compatibility, if needed.
if (mTranslator != null) {
mTranslator.translateEventInScreenToAppWindow(event);
}
// Enter touch mode on down or scroll.
final int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) {
ensureTouchMode(true);
}
// Offset the scroll position.
if (mCurScrollY != 0) {
event.offsetLocation(0, mCurScrollY);
}
// Remember the touch position for possible drag-initiation.
if (event.isTouchEvent()) {
mLastTouchPoint.x = event.getRawX();
mLastTouchPoint.y = event.getRawY();
mLastTouchSource = event.getSource();
}
return FORWARD;
}
}
ImeInputStage
输入法事件处理阶段,处理一些输入法字符等。如果对输入的内容无法识别,则继续往下转发。
final class ImeInputStage extends AsyncInputStage
implements InputMethodManager.FinishedInputEventCallback {
@Override
protected int onProcess(QueuedInputEvent q) {
if (mLastWasImTarget && !isInLocalFocusMode()) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
final InputEvent event = q.mEvent;
int result = imm.dispatchInputEvent(event, q, this, mHandler);
if (result == InputMethodManager.DISPATCH_HANDLED) {
return FINISH_HANDLED;
} else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
// IME 无法处理,则往下转发
return FORWARD;
} else {
// 会执行异步回调
return DEFER;
}
}
}
return FORWARD;
}
}
ViewPreImeInputStage
视图预处理输入法事件阶段,将输入法的事件派发到视图的树。
final class ViewPreImeInputStage extends InputStage {
@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;
}
}
}
NativePreImeInputStage
本地方法预处理输入法事件阶段,可用于实现类似adb 输入
的功能。
final class NativePreImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {
public NativePreImeInputStage(InputStage next, String traceCounter) {
super(next, traceCounter);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
mInputQueue.sendInputEvent(q.mEvent, q, true, this);
return DEFER;
}
return FORWARD;
}
@Override
public void onFinishedInputEvent(Object token, boolean handled) {
QueuedInputEvent q = (QueuedInputEvent) token;
if (handled) {
finish(q, true);
return;
}
forward(q);
}
}
}
经过{综合性处理阶段}
到{视图处理阶段}
到{本地处理阶段}
,接着从{早起输入法处理阶段}
到{输入法阶段}
到输入法视图预处理阶段
最后到{输入法本地预处理阶段}
。
当然按照正常情况肯定是在职责链靠前的阶段,被处理的机会越大。(这不是废话么:-P)
。我们最关心的视图的触摸事件的派发的阶段就在{视图处理阶段}
中。
结束语
下一篇文章将会分析,如何将 inputEvent 事件触达到 dispatchTouchEvent 。
View·从 InputEvent 到 dispatchTouchEvent 源码分析(二)
参考资料:
网友评论