Flutter WebView 下无法长按粘贴的问题


解决过程

如题,在使用 Flutter WebView 过程中,测试反馈了一个 Web 页面无法长按出现「粘贴」按钮的问题。

当我看到这个问题时,第一反应是 WebView 的开源库可能有 bug,查了相关 issue,发现在 Flutter 中嵌 Native View 的水有点深,后续扩展详聊。

这里先记录解决问题思路和步骤:

  • 官方提出 Android 下需要 Flutter 1.22 版本之后使用 Hybrid composition 模式来嵌入 View 才不会导致部分键盘处理和辅助功能丢失,由于我的应用场景是 WebView,不可能抛弃键盘相关的功能,所以只能这么干

  • 我使用的 WebView 库是 inappwebview,支持 Hybrid composition 得是 master 分支的 5.0.0 版本,但是作者消失了一段时间,迟迟没有发布新版本,我考虑直接使用 git master 分支作为依赖源(如果作者还玩失踪,甚至有点想自己维护一个 flutter webview 库)。

  • 参考 inappwebview 文档,Android 下添加了 useHybridComposition 配置,但是依旧无法长按出现「粘贴」按钮

  • 我拿官方的 webview_flutterinappwebview 分别写了一个最小化的 Demo,发现都能长按出现「粘贴」按钮,那就是我项目里边代码的问题了

  • 长按是手势事件,项目中代码和最小化的 Demo 中关于这一部分的差异就是我们 Flutter 页面最外层加了一个 GestureDetector 来简化切换页面时的键盘收起问题,我尝试去掉这个组件问题就解决了

  • 最终问题就是这个 GestureDetector 为什么会拦截掉 Native View 的长按事件,查了一通文档,发现 AndroidView/PlatformViewSurface/UiKitView 如果外层有 GestureDetector 之类的手势事件监听,并且要参与手势事件竞争的话,需要配置 gestureRecognizers

  • 最后就解决了,根据官方文档,传入对应的配置:

    gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
                          new Factory<OneSequenceGestureRecognizer>(
                              () => EagerGestureRecognizer())
                        ].toSet()
    

    EagerGestureRecognizer 基本就是提供用来给 AndroidView/PlatformViewSurface 声明要抢占各种手势事件的了。

下边写一下这次 debug 过程中了解到的 Flutter UI 嵌入原生 View 的一些东西。

Flutter 内嵌 Native View

Flutter 允许我们在 Flutter 的 UI 界面上嵌入原生的 View,参考官网的指引:

Hosting native Android and iOS views in your Flutter app with Platform Views

我们使用 Flutter WebView 时,也就是在我们的 Flutter UI 页面中嵌入了一个 WebView 的 View。这已经涉及原生层面的 View 了,所以在 iOS 和 Android 实现上一定有所不同。

WebView 的实现上,iOS 走 WKWebView,Android 走 android.webkit.WebView,在 Flutter 嵌入原生 View 的实现上,两端也有比较大区别:

Android 在 Flutter UI 嵌入原生 View 时有两种实现方式,具体的实现步骤参考官网文档,这里关注的是它们之间的区别:

  1. Virtual displays

    这个模式会将 android.view.View 渲染为纹理,主要是 AndroidView 使用 TextureLayer 来实现,它不会嵌入到 Android Activity 的视图层次结构中,某些原生的交互,如键盘处理和辅助功能可能会无法工作。

    官方性能部分提及,这个模式可能会导致浪费显存和绘制性能,但是由于不和其他线程竞争,在处理动画上有优势。

    所以当你的应用使用原生 View 是纯展示类型的,可以考虑使用该模式

  2. Hybrid composition

    在 Flutter 1.22 版本之后,支持将 android.view.View 集成到视图层次结构中,对于键盘处理和辅助功能是完整支持的,但是在 Android 10 版本之前,可能会降低 UI 帧吞吐量。

iOS 只有一种方式,就是 Hybrid composition,直接将 UIView 嵌入到视图层级中,简单暴力。官网文档也稍有提及,在 Flutter UI 中嵌入 View 是比较昂贵的操作,会损耗性能。

优化方案

如果 Android 10 版本之前使用 Hybrid composition,或者 iOS 的 View 真的出现 Flutter 嵌入 View 导致的相关性能问题时怎么办?

暂时想到的解法就是抛弃在 Flutter UI 中嵌入 WebView 的做法,可以考虑:

  • 反过来实现,用一个 Android Native View 嵌我们需要的 Flutter UI 部分
  • 如果 Flutter UI 部分内容不多,可以考虑整个 View 都使用 Native 实现,例如你只是要打开个定制的 Web 页面这种业务场景