Android 中点击事件的判断
最近实验室做的一个东西会向 Android 应用快速注入一系列触屏事件,模拟用户的点击。但是我们发现当按下和弹起的 MotionEvent 之间时间间隔过小(例如小于 100ms)时,会导致该事件被忽略。看了代码后发现 Android 中按下和弹起之间时间间隔要在 115ms 以上才会被认为是一个点击事件。这里结合 Android 的源码分析一下点击事件的产生过程。
当用户触摸屏幕后,底层驱动会将这一事件包装成一个 MotionEvent 对象,传递给 WindowsManagerService。后者维护了一个事件队列,会将事件分发给正获得焦点的控件,其中,dispatchPointer 方法负责将触屏事件发送给当前活动窗口(Window)。每一个 Window 都有一个 ViewRoot 实例接收来自 WindowManagerService 的消息。活动窗口的 ViewRoot 在 handleMessage 方法中接收了这一触屏事件后,就会调用最上层的 View 对象的 dispatchTouchEvent 方法。和大多数图形系统一样,Android 中的 view 也是以树状结构组织的,事件会从最上层依次向下层传递,直到某一结点能处理这一事件为止。
View 对象的 onTouchEvent 负责处理触屏事件,这里也是点击事件产生的地方,下面这段代码创建了一个点击事件,并把它添加到事件链中,在触屏事件处理完成后再执行。
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
而在前面的判断条件可以看到,这一点击事件创建的前提是 mPrivateFlags 的 PRESSED 位和 PREPRESSED 位同时被置上 (View.java):
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
...
}
接着看触屏按下事件的处理 (View.java):
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
mPrivateFlags 的 PREPRESSED 位会被立即置上,另外 postDelayed 方法会在经过 ViewConfiguration.getTapTimeout() 时间后,执行一个 CheckForTap 的实例,看来这就是我们要找的东西了。CheckForTap 是一个很简单的 Runnable 对象,它所做的就是把 mPrivateFlags 中除了 PREPRESSED 的位都清零,再把 PRESSED 位置上,并注册一个长按事件的检测对象。而 ViewConfiguration.getTapTimeout() 的默认值便是 115ms。注释中还说明了这个延时的用意在于区分滑动操作和点击操作,如果触屏区域在这一时间内没有移动,这个操作才会被识别成点击。