【问题标题】:Missing touch events when work is done in UI thread在 UI 线程中完成工作时缺少触摸事件
【发布时间】:2019-05-21 15:44:00
【问题描述】:

我正在开发一个 Android 键盘,它基本上是一个自定义的 LinearLayout(命名为 KeyboardView)和 LinearLayouts(代表行),它有一组 TextViews(代表键)。如果它应该用 XML 表示,它会是这样的:

<KeyboardView>       <!-- extends LinearLayout -->
    <LinearLayout>   <!-- row #1 -->
        <TextView /> <!-- key #1 -->
        ...          <!-- more TextViews -->
    </LinearLayout>
    ...              <!-- more rows/LinearLayouts -->
</KeyboardView>

KeyboardView 中,我覆盖onTouchEvent,并且如果用户在TextViews 之一的范围内触摸,则调用控制器(并且该控制器确定应如何处理键)。这意味着KeyboardView 的孩子永远不会获得焦点事件。出于各种原因以这种方式完成,例如因此可以处理在键盘上的滑动。

这在大多数情况下都可以正常工作,但我们收到了一些关于键盘并不总是对所有触摸事件做出反应的报告。可能很难重现这种行为,但我们已经确认它不是由用户触摸按键之间的间隙引起的(因为没有间隙),所以我们认为在某些情况下我们需要花费太多时间来弄清楚应该用密钥做什么 - onTouchEvent 的执行时间可能太长了。

为了验证这个理论,我最终在onTouchEvent 中添加了一些Thread.sleeps(以模拟正在完成的大量工作)。这似乎以某种方式重现了这个问题,因为很明显不是所有的触摸事件都被接收到。似乎系统有某种队列,以便接收一些事件,但忽略一些事件。我例如制作了一个简单的键盘,只有 ABCD 键,睡眠时间为 2 秒。当一个接一个地按下它们时,只会收到ABD 的触摸事件(ACTION_DOWNACTION_MOVEACTION_UP)。但是在某些情况下会收到ACTION_CANCEL

为了夸大问题,我尝试将睡眠设置为5秒,并在第一次按键触摸的睡眠期间单击第二个按键。在这种情况下,不会收到第二次点击的任何事件 - 但会收到第一次按键的所有触摸事件。

对于上述场景,三个接收到的事件如下(输出到日志时)。请注意,第二次按键点击(从未收到)在日志 #1 和 #2 之间:

MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=181.0, y[0]=536.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=116942926, downTime=116942926, deviceId=8, source=0x1002 }
MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=181.0, y[0]=536.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=116942992, downTime=116942926, deviceId=8, source=0x1002 }
MotionEvent { action=ACTION_UP, actionButton=0, id[0]=0, x[0]=181.0, y[0]=536.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=116942999, downTime=116942926, deviceId=8, source=0x1002 }

我不知道它只是 Android 框架的一部分还是我遗漏了一些东西。我可以“只是”将完成的工作 onTouchEvent 放到一个线程中,但我担心它只是涵盖了真正的问题,而且我通常更愿意尽可能避免使用线程。我也尝试过覆盖onInterceptTouchEvent 并始终让它返回true(以尽量避免ACTION_CANCEL),但在某些情况下仍然会出现ACTION_CANCEL

【问题讨论】:

    标签: android event-handling touch-event motionevent


    【解决方案1】:

    可以组合触摸事件,尤其是移动事件。你在检查吗?

    触摸事件被完全跳过的可能性为 0,框架不会这样做。如果其类型相同,则确实会发生组合。此外,如果您收到 CANCEL,则意味着触摸事件将转到另一个视图,并且在所有手指都抬起之前您将不会再收到任何信息 - 因此,如果您看到取消,您应该找出谁在窃取您的触摸。 onInterceptTouchEvent 不会解决这个问题,因为这只会影响你的孩子,而不是你。如果有的话,这会让事情变得更糟。

    另外,检查 ACTION_POINTER_DOWN 和 ACTION_POINTER_UP。如果多个手指同时触摸,这些会发生而不是上下。这些可能是您不小心造成的(或者屏幕可能注册错误,尤其是当您几乎要触摸时,有时可能会有杂散电容)。

    【讨论】:

    • 多次点击的有趣想法。然而,情况似乎并非如此(如果我理解正确的话)。我已经用日志输出更新了帖子,说明了第二种情况下的三个运动事件(睡眠时间为 5 秒),似乎在所有三种情况下都只有一个指针。
    【解决方案2】:

    我终于发现,当我这样设置时,有些事件被忽略了。当添加 5 秒的睡眠时,系统会输出以下内容(后面是使用 CPU 的列表):

    2019-06-11 17:06:45.200 905-990/? E/ActivityManager: ANR in [my_app_identifier]
    PID: 10365
    Reason: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago.  Wait queue length: 12.  Wait queue head age: 5490.6ms.)
    Load: 0.0 / 0.0 / 0.0
    CPU usage from 284899ms to 0ms ago (2019-06-11 17:02:00.002 to 2019-06-11 17:06:44.902) with 99% awake:
    

    这就是我认为导致某些事件不幸被忽略的原因。因此,在这种情况下,休眠线程不是有效的测试。

    最后,我从 Gabe Sechan 的回答中得到启发,因为从逻辑上讲,当用户快速键入时,他们可能会在抬起最后一个手指之前用另一根手指触摸。所以现在我们也支持ACTION_POINTER_DOWNACTION_POINTER_UP,这似乎工作得更好了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多