我的编程空间,编程开发者的网络收藏夹
学习永远不晚

混合栈跳转导致Flutter页面事件卡死问题解决

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

混合栈跳转导致Flutter页面事件卡死问题解决

问题来源

在我们升级Flutter2.5后,测试在走整个业务流程中发现了有页面卡死现象,于是给我提了一个BUG。

在xx页面多次操作后,页面卡死,页面还可以滚动但是无法跳转,点击长按事件都失效了。

在我多次测试后发现,确实存在这个问题,而且老版本也都存在。

问题难点

复现难

问题定位

最开始,我先确定了失效情况下,事件源头有没有正确发送,所以,先在_dispatchPointerDataPacket方法上添加了断点。结果发现都是正常。其实也好理解,页面可以滚动,说明引擎层发送事件肯定是正常的。

在进行一系列没有用的断点定位后发现,正常事件的hitTestResult(事件中命中测试阶段收集的所有能够响应事件的RenderObject节点)和错误页面的hitTestResult_path数量不一样。

正常的hitTestResult

错误的hitTestResult 

经过对比发现,错误的列表到RenderPointerListener这个就停止了,我看这名字还挺熟悉,难道跟IgnorePointer有啥关系?我通过这个RenderObject节点的parent一层一层往上找,发现是ScrollableState中使用了IgnorePointerScrollableState是列表组件如ListViewSingleChildScrollView等底层使用的Widget State)

//...
Widget result = _ScrollableScope(
  scrollable: this,
  position: position,
  child: Listener(
    onPointerSignal: _receivedPointerSignal,
    child: RawGestureDetector(
      key: _gestureDetectorKey,
      gestures: _gestureRecognizers,
      behavior: HitTestBehavior.opaque,
      excludeFromSemantics: widget.excludeFromSemantics,
      child: Semantics(
        explicitChildNodes: !widget.excludeFromSemantics,
        child: IgnorePointer(
          key: _ignorePointerKey,
          ignoring: _shouldIgnorePointer,
          ignoringSemantics: false,
          child: widget.viewportBuilder(context, position),
        ),
      ),
    ),
  ),
);
//...

这里会通过_ignorePointerKey来把滚动区域及其子节点的事件都屏蔽了。那么什么时候_ignorePointerKey会被置为true呢。

通过了解ScrollableState源码发现,只要页面在滚动过程中,_ignorePointerKey就会被置为true,当手指抬起时,才会将_ignorePointerKey重新置为false

通过多次断点和日志输出发现,当我从后面的页面返回到目标页面时,第一次滚动时,就触发了ScrollableStatesetIgnorePointer_ignorePointerKey置为true了,但是后面再无事件将_ignorePointerKey置为false了,此后,再滚动页面时,也无法触发setIgnorePointer方法。

到这里,想继续调试,就需要比较熟悉Flutter的事件原理了,因为这里我只想讲一下我解决这个问题的思路,所以Flutter原理的知识不多讲。后面经过一系列调试发现,问题出在OneSequenceGestureRecognizer这个抽象类中

abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
  //...
  @protected
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
    // 将当前指针和当前的handleEvent方法添加到全局指针识别器中存储缓存起来
    GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
    _trackedPointers.add(pointer);
    assert(!_entries.containsValue(pointer));
    _entries[pointer] = _addPointerToArena(pointer);
  }
  @protected
  void stopTrackingPointer(int pointer) {
    if (_trackedPointers.contains(pointer)) {
      // 从全局指针中移出当前指针
      GestureBinding.instance!.pointerRouter.removeRoute(pointer, handleEvent);
      _trackedPointers.remove(pointer);
      // 如果_trackedPointers是空的
      if (_trackedPointers.isEmpty)
        didStopTrackingLastPointer(pointer);
    }
  }
}

OneSequenceGestureRecognizer这个类的作用是当存在多个手势时,只响应一个手势。比如我同时两个手指点击一个按钮,按钮的点击事件也只会触发一次。像我们常见的TapGestureRecognizerVerticalDragGestureRecognizerHorizontalDragGestureRecognizer等最终都是实现的这个类。

在这个类中startTrackingPointer方法会在手指按下后,也就是发生PointerDownEvent时将当前类的handleEvent添加到全局指针识别器中,并且将这个pointer(可以看做指针id)添加到_trackedPointers中缓存起来,可以这样理解,这个方法就是一次手势的开始。

当发生PointerUpEvent等事件时,会调用stopTrackingPointer事件,将手势移除,这就标志着手势的结束。

其中有个_trackedPointers.isEmpty判断,会调用didStopTrackingLastPointer方法,这个方法一般是将手势识别器的状态置为ready。经过我多次对问题页断点发现,无论如何都调不到这个方法,也就是说_trackedPointers里面一直有个手势指针没有被移除。

这里我要介绍一下VSCode一个调试方法。因为我还不知道问题的根源,所以我复现问题是通过不断点击页面同时触发页面跳转来达到的,而且只是有几率复现。所以我无法通过断点来确定这里为何有手势事件没有调用stopTrackingPointer,所以我使用了VSCode的LogPoint方式来对整个过程进行日志输出。

在不断复现问题查看日志中发现,在跳转页面前,会有指针事件被添加进_trackedPointers,但是却没有调用stopTrackingPointer方法就跳转到新页面了。

tap 4. addAllowedPointer (tap.dart) _down != null = true 637436658
tap 5. _trackedPointers add 195 502831342 handleEvent: 931478062
tap 5. _trackedPointers add 195 21393736 handleEvent: 790157058
tap 5. _trackedPointers add 195 126324365 handleEvent: 160402385
onNativeRouteEvent: (9): NativeRouteEvent.onCreate
onNativeRouteEvent: (8): NativeRouteEvent.onPause
onFlutterRouteEvent: (9): FlutterRouteEvent.onPush

问题确定

由于我们是混合栈项目,我们是自己写的一套混合栈路由管理,类似FlutterBoost,在进行页面跳转时,会将FlutterEngine先detach,然后再跳转。在Flutter的Android发送事件源码里面,会对FlutterEngine是否attach进行判断,然后触发Flutter Framework一系列处理。

@Override
  public boolean onTouchEvent(@NonNull MotionEvent event) {
    // 这里判断是否attach
    if (!isAttachedToFlutterEngine()) {
      return super.onTouchEvent(event);
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      requestUnbufferedDispatch(event);
    }
    return androidTouchProcessor.onTouchEvent(event);
  }

这里由于页面跳转时如果还有事件在处理(比如手指按下并没有抬起),那么跳转后,Flutter再也接收不到手指抬起的事件了,所以_trackedPointers就一直不被正确移除,导致了事件异常。由于是我们自己写的混合栈库,所以修改起来也简单。

问题解决

Android

public class XXXFlutterView extends FlutterView {
  // ...
  @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        try {
            AndroidTouchProcessor androidTouchProcessor;
            Field field = this.getClass().getSuperclass().getDeclaredField("androidTouchProcessor");
            field.setAccessible(true);
            androidTouchProcessor =  (AndroidTouchProcessor)field.get(this);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                requestUnbufferedDispatch(event);
            }
            return androidTouchProcessor.onTouchEvent(event);
        } catch (Exception e) {
            e.printStackTrace();
            return super.onTouchEvent(event);
        }
    }
}

我们本身有一个继承于FlutterView的类,在其中实现一下父类的onTouchEvent方法,把isAttachedToFlutterEngine的判断去掉即可,由于androidTouchProcessor是私有类,所以这里我使用了反射。

iOS解决思路还不太一样,在新的Flutter版本中,iOS提供了forceTouchesCancelled方法来取消Flutter中的事件,所以iOS是通过在混合栈中detach前,手动调用一下这个方法来解决这个问题的。

总结

由于对Flutter事件很多细节掌握的不够到位,所以这个问题从定位问题到最终解决差不多花了一周时间,解决过程中也加深了我对Flutter事件的理解。

以上就是混合栈跳转导致Flutter页面事件卡死问题解决的详细内容,更多关于混合栈Flutter页面卡死的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

混合栈跳转导致Flutter页面事件卡死问题解决

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

混合栈跳转导致Flutter页面事件卡死问题解决

这篇文章主要为大家介绍了混合栈跳转导致Flutter页面事件卡死问题解决,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录