一步步探索学习Android Touch事件分发传递机制(二)

本文探讨了Android中Touch事件的分发机制,重点分析ACTION_UP和ACTION_MOVE事件的分发规律及其与ACTION_DOWN事件的关系。通过实验验证了不同情况下事件的传递路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言
1. 探究的内容
  • 在上一篇文章《一步步探索学习Android Touch事件分发传递机制(一)》中,已经以ACTION_DOWN事件为例,对Android系统的Touch事件分发传递机制做了探究,并得出了形象好记忆的结论。

  • 这篇文章所探究的问题是与上一篇文章紧密相关的,如果对Android Touch事件分发传递机制还不太了解的同学,建议先去看看上一篇文章《一步步探索学习Android Touch事件分发传递机制(一)》

  • 我们知道,一个操作,比如一个点击事件,是由多个不同TYPE的MOTION_EVENT组成的。for example,点击事件是由一个ACTION_DOWN事件和一个ACTION_UP事件组成的。那么诸如ACTION_UP和ACTION_MOVE事件是不是跟ACTION_DOWN事件一样遵循相同的分发规律呢?

  • 事实上,答案是否定的。 ACTION_UP和ACTION_MOVE事件的分发传递流程与这之前的ACTION_DOWN事件如何传递以及在哪里被消费有密不可分的联系。具体我们下面一步步探索了解。

2. 探究的方法
  • 我们还是继续以上一篇文章《一步步探索学习Android Touch事件分发传递机制(一)》中所写的Demo为例子,一步步打Log,去探究整个事件的传递流程,再用绘图描述他的机制。

  • 当然知其然须知其所以然,对于Android源码的分析,将会在下一篇文章《一步步探索学习Android Touch事件分发传递机制(三)》中分析。


Demo中见分晓
1.Demo代码
2.打Log,找规律,识机制

1. )所有方法(dispatchTouchEvent(); onInterceptTouchEvent(); onTouchEvent() )都return super的情况:

  • 首先,我们保持所有方法都return super,当然这个我上一篇文章探究过,ACTION_DOWN事件会以类U型的传递路线在View树中分发传递。

  • 那么ACTION_UP和ACTION_MOVE事件呢?这里以ACTION_UP事件为例做探究。(事实上,Action_move事件与Action_up事件是遵循类似规律的。)我对着Demo屏幕中的View做一个点击操作(前面交代过,点击事件是由一个ACTION_DOWN事件和一个ACTION_UP事件组成的)。

  • 打log:
    这里写图片描述

    注:上图中,紫色框内的是点击事件的ACTION_DOWN的分发流程;绿色框中的是点击事件的ACTION_UP事件的分发流程。后面的图同理。

  • 规律:可以看到,ACTION_UP事件并没有按照类U型的结构去在View树传递,而是直接在Activity的onTouchEvent方法中消费掉了。

  • 绘图如下:
    这里写图片描述

2. )ViewGroup2的dispatchTouchEvent() return true的情况:

  • 然后我们来探究ACTION_DOWN事件在dispatchTouchEvent()中被消费掉的情况下,ACTION_UP事件是怎么传递分发的。

  • 令ViewGroup2的dispatchTouchEvent() return true,打log:
    这里写图片描述

  • 规律:当ACTION_DOWN事件在某一个View或者ViewGroup的dispatchTouchEvent()方法中被消费掉的情况下,对应的ACTION_UP事件也会在此被消费掉,终止传递。

  • 绘制成图:
    这里写图片描述

3. )ViewGroup2的onInterceptTouchEvent()和onTouchEvent() 都 return true的情况:

  • 也就是让ViewGroup2拦截掉事件,并且由它自己来消费事件。
    这里写图片描述

  • 打log:
    这里写图片描述

  • 规律:可以看到,当ACTION_DOWN事件在某一个View或者ViewGroup的onTouchEvent()方法中被消费掉的情况下,对应的ACTION_UP事件也会在此被消费掉,终止传递。

    但是值得注意的是,这种情况下,ACTION_UP事件是不会再经过拦截器onInterceptTouchEvent()方法了的。

  • 绘图如下:
    这里写图片描述

  • 4.) ViewGroup2的onTouchEvent() return true的情况:

  • 就是让VIewGroup2的onTouchEvent()方法在接收到View的onTouchEvent()方法传递过来的ACTION_DOWN事件时将其消费掉。看这种情况下,ACTION_UP事件是怎么传递的。

  • 打Log:
    这里写图片描述

  • 规律:可以从Log看出来,当ACTION_DOWN事件被某控件的onToucEvent()方法消费掉,则其对应的ACTION_UP事件只传递到该控件。也就是不会传递到比此控件更深层的控件中去。

  • 绘制图:
    这里写图片描述


总结归纳
  • ACTION_MOVE事件与ACTION_UP事件遵循类似的规律,上面只以ACTION_UP为例子分析探究。

  • ACTION_MOVE事件与ACTION_UP事件的传递分发与其对应之前的ACTION_DOWN事件有紧密联系。

  • 具体的,当ACTION_DOWN事件在dispatchTouchEvent()方法中被消费,则对应的ACTION_MOVE事件与ACTION_UP事件也会从上而下传递到该控件的该方法处被消费掉。

  • 当ACTION_DOWN事件在onTouchEvent()方法中被消费,则对应的ACTION_MOVE事件与ACTION_UP事件只传递到该控件处的onTouchEvent()方法中然后被消费而终止传递。不会经历该控件之下的控件的传递过程。

  • 当所有方法都默认return super,则ACTION_MOVE事件与ACTION_UP事件会在Activity的onTouchEvent()中被消费掉。

当我们在 Android 开发过程中遇到分发事件失效的情况时,Logcat 中记录的日志信息是非常宝贵的线索来源。为了帮助定位问题原因并修复 bug,我们应该学会从日志中提取有用的信息。下面将详细介绍如何解读与分发事件相关的 Logcat 日志以及可能出现的相关错误提示。 ### 1. 正常的触摸事件流 在正常情况下,每当用户触碰屏幕时,系统会按照以下顺序传递 `MotionEvent` 给合适的视图组件进行处理: - Activity -> DecorView (根布局) -> ViewGroup -> View 每一步都会触发相应的生命周期回调方法如 `dispatchTouchEvent()`, `onInterceptTouchEvent()`, `onTouchEvent()` 等。如果一切顺利的话,你应该能在 Logcat 中看到类似这样的输出: ```plaintext D/InputTransport( 9746): Channel 'abcde' (~APP_NAME) touched (type=DOWN) V/WindowManager( 9746): Delivering touch to window: Window{...}, action=ACTION_DOWN, x=..., y=... D/ActivityThread( 9746): Performing launch of ... V/MyCustomViewGroup( 9746): onTouchEvent ACTION_DOWN at point(x,y) ``` 以上是一段简化的日志片段,展示了典型的触摸事件是如何一步步被各个层次捕获和响应的过程。 ### 2. 分发事件失效的症状及对应日志特征 然而有时候由于某些意外状况的发生(例如配置不当、逻辑缺陷或是竞态条件),原本应该正确流转下去的触摸事件可能会突然终止或者丢失不见。这时我们就说发生了“分发事件失效”。以下是几种常见的异常现象及其对应的典型日志描述: #### A. **事件未到达目标** 如果你发现点击某个按钮却没有反应,并且怀疑是因为它的父容器提前拦截走了所有交互,则可以留意是否存在如下字样出现在 Logcat 输出之中: ```plaintext I/choreographer( 9746): Skipped X frames! The application may be doing too much work on its main thread. W/InputEventReceiver( 9746): Attempted to finish an input event but the receiver has already been disposed. E/ViewRootImpl@xxxxxxx[YourActivity]( 9746): sendUserActionEvent() mView == null ``` 上面这几行意味着主线程过于忙碌以至于无法及时消耗掉来自输入管理服务的消息队列内容,最终造成部分或全部手势指令未能成功抵达目的地。 #### B. **事件被捕获但无回应** 另一种情形是虽然我们知道某一特定区域确实获得了焦点并且也收到了相应类型的触摸信号却没有任何视觉反馈呈现出来。此时我们可以查找是否有任何关于自定义监听器注册失败或者是回调函数为空指针引用之类的警告存在: ```plaintext W/System.err( 9746): java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.view.View.performClick()' on a null object reference at com.example.MyClass.onClick(MyClass.java:X) ``` 这表明试图调用某不存在的对象上的方法而导致崩溃发生。当然还有可能是其它形式的原因,比如忘记设置正确的属性值之类的问题所引起的结果亦同。 #### C. **事件循环出现问题** 此外若涉及到较高级别的架构设计则还需要特别关注那些涉及跨进程通信(IPC),线程同步机制等方面的内容。假如应用程序依赖于 HandlerLooper 架构来协调任务分配却又遭遇了阻塞等待超时的情形那往往会在 Logcat 上留下痕迹类似于下述表达式: ```plaintext E/MonitoringInstr( 9746): Exception thrown while dispatching to activity android.os.DeadObjectException: Transaction failed on small parcel; remote process probably died at android.os.BinderProxy.transactNative(Native Method) at android.os.BinderProxy.transact(BinderProxy.java:X) ``` 该报错说明客户端尝试发送请求给远程对象的时候因为对方已经处于不可达状态而遭到拒绝回复。 ### 3. 解决建议 针对上述提到的各种潜在风险点,我们需要采取针对性措施加以防范避免不必要的麻烦: - **优化性能** :尽量减少 UI 更新频率确保流畅体验的同时也要注意不要占用过多 CPU 时间片以免影响到其它后台作业。 - **增强健壮性** :为每一个关键操作都设定好边界条件检查防止非法参数传入从而降低程序出错几率;另外记得合理组织代码结构提高可读性和复用度以便日后排查故障时更容易上手。 - **强化测试覆盖** : 编写充分单元测验模拟真实世界的多样环境尽可能多地暴露隐藏隐患直至彻底消除为止。 总之通过对 Logcat 的细心分析加上科学有效的预防手段相结合就能大大提高解决问题效率保证产品质量始终维持在一个较高的水准线上。 --- 如果您想了解更多有关 Android 输入系统的细节知识或者其他技术要点,欢迎继续提问!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值