当前位置: 代码网 > it编程>App开发>苹果IOS > 混合栈跳转导致Flutter页面事件卡死问题解决

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

2024年05月19日 苹果IOS 我要评论
问题来源在我们升级flutter2.5后,测试在走整个业务流程中发现了有页面卡死现象,于是给我提了一个bug。在xx页面多次操作后,页面卡死,页面还可以滚动但是无法跳转,点击长按事件都失效了。在我多次

问题来源

在我们升级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页面卡死的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com