Flutter webview_flutter pigeon 通信桥实现原理

webview_flutter 同时支持多个平台(Android、iOS、Web),对各平台下的原生代码进行封装,导出一套统一的 Dart 接口,其中使用到由 Flutter 团队开发的 pigeon 框架。

pigeon 框架基于代码生成技术,专门用于 Flutter 跨端场景下,多端原生实现与 Flutter 的高效打通。对 pigeon 感兴趣的同学,请自行了解。本文中假设已有 pigeon 开发经验。

回到 webview_flutter,为了后续研究原生侧实现,需要首先梳理清楚 Dart 与原生侧的通信桥,这也是本文的主题。

webview_flutter_platform_interface

声明了各平台通用接口,这个接口允许 webview_flutter 插件的平台特定实现,以及插件本身,确保它们支持相同的接口。

如果要实现一个新的针对 webview_flutter 的平台特定实现,你需要扩展 WebviewPlatform,并提供一个执行平台特定行为的实现。当你注册你的插件时,通过调用 WebviewPlatform.instance = MyPlatformWebview() 来设置默认的 WebviewPlatform

WebViewPlatform

对于要实现 WebView 的平台,都应当实现该接口。它定义了一些核心方法:

这四个方法分别对应于 webview_flutter 中的四个核心类,将在后文中分别介绍。这四个类,承接了 webview_flutter 中的大多数功能,因此,这四个类,也是分析这个库的“四大入口”。

各平台实现如下:

关于 WebViewPlatform 的具体使用,在后续小节中,我以 Android 为例进行了分析,进行梳理。现在,让我们继续,把 webview_flutter_platform_interface 里面的几个接口,都看一看。

这个类定义了一个抽象的 cookie 管理器,它可以被不同的平台(如 Android、iOS)扩展,以提供平台特定的 cookie 管理功能。

该类提供了两个核心方法:

PlatformNavigationDelegate

这个类是一个抽象的代理,它定义了一些方法来处理在 webview 上发生的导航事件,如页面加载、页面跳转等。具体的行为需要由实现这个接口的类来提供。

该类核心方法:

PlatformWebViewController

这个类定义了一个抽象的 web view 控制器,它可以被不同的平台(如 Android、iOS)扩展,以提供平台特定的 web view 控制功能。

该类中定义了可由开发者操作的 WebView 方法,比如跳转 url、前进后退、运行 JavaScript 等。

对于这个类,我在《PlatformWebViewController》这篇笔记中进行了专门介绍,可点击阅读。

PlatformWebViewWidget

这个类可以理解是 WebView 的工厂类,它有一个 build 方法,返回一个 Flutter Widget,即 Flutter 侧的 WebView(基于 PlatformView)。

AndroidWebViewPlatform

上面四个核心类分析完,我们回到 WebViewPlatform,这里以 Android 平台为例,分析 AndroidWebViewPlatform。

此时也来到了 webview_flutter_android 包下,AndroidWebViewPlatform 实现了 WebViewPlatform 的四大方法:

显然,返回的这四个类,是对我们上面介绍的那四个接口的继承实现。

在这 4 个 Android 开头实现类中,他们还是 Dart 类,但是,他们内部各自包含一些 Dart 成员类属性,这些成员类属性负责与原生侧的通信了。

Pigeon 呢?

说了这么多,都是些普通接口,怎么不见 Pigeon 身影呢?

下面,我们进入 Pigeon 部分。

android_webview.dart

在 webview_flutter_android 工程下,有一个 pigeons 包,里面有一个 android_webview.dart,这是 Pigeon 的接口声明,它本身不参与 Dart 编译,

该类映入眼帘的一段代码,足够说明它绝非等同之辈:

@ConfigurePigeon(
  PigeonOptions(
    dartOut: 'lib/src/android_webview.g.dart',
    dartTestOut: 'test/test_android_webview.g.dart',
    dartOptions: //...,
    javaOut:
        'android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java',
    javaOptions: JavaOptions(
      package: 'io.flutter.plugins.webviewflutter',
      className: 'GeneratedAndroidWebView',
      copyrightHeader: //...
      ],
    ),
  ),
)

这是一段 Pigeon 的配置,告诉代码生成器,根据 android_webview.dart 中声明的接口,需要生成两份声明:

其中:

如果你感到有些抽象或吃力,说明首先需要补充一下 pigeon 相关基础。

下面我们来看 android_webview.dart 中定义的 pigeon 通信桥。

ConsoleMessage

先来一个简单的,这是一个实体类,定义了一条命令行日志:

class ConsoleMessage {
  late int lineNumber;
  late String message;
  late ConsoleMessageLevel level;
  late String sourceId;
}
@HostApi(dartHostTestHandler: 'TestCookieManagerHostApi')
abstract class CookieManagerHostApi {
  /// Handles attaching `CookieManager.instance` to a native instance.
  void attachInstance(int instanceIdentifier);

  /// Handles Dart method `CookieManager.setCookie`.
  void setCookie(int identifier, String url, String value);

  /// Handles Dart method `CookieManager.removeAllCookies`.
  @async
  bool removeAllCookies(int identifier);

  /// Handles Dart method `CookieManager.setAcceptThirdPartyCookies`.
  void setAcceptThirdPartyCookies(
    int identifier,
    int webViewIdentifier,
    bool accept,
  );
}

这是一个 HostApi 类,表示这个模块实现在 Android 侧的,生成的 Java 代码用于注入实现,生成的 Dart 代码用于调用。

来到生成的 dart 文件 android_webview.g.dart 下,找到 Dart 侧生成后的 CookieManagerHostApi,起内部的 setCookie、removeAllCookies 方法都是 Channel 调用,调用 Java 侧对应实现,并将结果返回 Dart 侧。

我们看谁继承了生成后的 CookieManagerHostApi,CookieManagerHostApiImpl 类继承了 CookieManagerHostApi。

CookieManager Dart 类中,将 CookieManagerHostApiImpl 作为类成员使用。

前面说到 AndroidWebViewPlatform 会创建 AndroidWebViewCookieManager 实例。 在 AndroidWebViewCookieManager 类中,使用 CookieManager 作为类成员。还记得 AndroidWebViewCookieManager 的父类吗?PlatformWebViewCookieManager,四大组成部分之一。

至此,我们知道了,AndroidWebViewCookieManager 内部通过层层转发,最终通过 pigeon 转到 Native 侧实现。

WebViewHostApi

继续如法炮制,回到 pigeon 接口的 WebViewHostApi,该类定义了对 WebView 主动操作的一些方法。找到 Dart 侧实现的 android_webview.g.dart 下的 WebViewHostApi,看谁用到它。

找到 WebViewHostApiImpl 继承自 WebViewHostApi。再看谁用到 WebViewHostApiImpl。

来到 android_webview.dart(另一个)下的 WebView 类,以 WebViewHostApiImpl 作为成员。谁用到 WebView?

来到 AndroidWebViewController,它内部以 WebViewHostApiImpl 作为成员。

现在知道 AndroidWebViewController 是怎么控制 WebView 的了,它也通过内部层层转发,最终通过 pigeon 转到 Native 侧实现。

WebViewFlutterApi

最后再分析一个压轴的——WebViewFlutterApi,顾名思义,他是在 Dart 侧触发 Native 创建 WebView 的。尽管任务重,但是 WebViewFlutterApi 接口声明很简单:

@FlutterApi()
abstract class WebViewFlutterApi {
  /// Create a new Dart instance and add it to the `InstanceManager`.
  void create(int identifier);
}

简单明了,只有一个 create 接口。

但是,这里面有一个注意点,这个模块是 FlutterApi,说明他的实现在 Flutter 侧,原生侧只是调用。

android_webview.g.dart 中会生成 WebViewFlutterApi 的注入类,并提供 setup 供 Dart 侧注入实现。实现的具体注入位于 AndroidWebViewFlutterApis 的 ensureSetUp 方法。传入的实现类是 WebViewFlutterApiImpl,该类继承自 WebViewFlutterApi。我们看 create 的实现:

@override
void create(int identifier) {
  instanceManager.addHostCreatedInstance(WebView.detached(), identifier);
}

这什么意思?调用这个方法时,Java 侧的 WebView 已经创建好了,将 instanceManager 中,对这个 Java 实例的唯一标识通过 pegion 传到 Dart 侧,Dart 侧将其保存到 Dart 这边的 instanceManager 中,并且将 identifier(俗称句柄)与 Dart 侧的 WebView 相关联。

再看看 Java 侧这个接口是什么时候调用的。位于 WebViewFlutterApiImpl,其 create 调用:

public void create(@NonNull WebView instance, @NonNull WebViewFlutterApi.Reply<Void> callback) {
  if (!instanceManager.containsInstance(instance)) {
    api.create(instanceManager.addHostCreatedInstance(instance), callback);
  }
}

可以看到,现在 Java 侧的 instanceManager 存一下,然后再调往 Dart 侧的通信。

WebViewFlutterApiImpl 的 create 又是谁调用的呢?来到了 WebViewPlatformView 类,他是 Java 侧的 Android WebView 实现类。WebViewFlutterApiImpl 是 WebViewPlatformView 的内部成员。

在 WebViewPlatformView 的构造方法中,同时会创建 WebViewFlutterApiImpl 实例:

@VisibleForTesting
WebViewPlatformView(
    @NonNull Context context,
    @NonNull BinaryMessenger binaryMessenger,
    @NonNull InstanceManager instanceManager,
    @NonNull AndroidSdkChecker sdkChecker) {
  super(context);
  currentWebViewClient = new WebViewClient();
  currentWebChromeClient = new WebChromeClientHostApiImpl.SecureWebChromeClient();
  // 创建 WebViewFlutterApiImpl 实例
  api = new WebViewFlutterApiImpl(binaryMessenger, instanceManager);
  this.sdkChecker = sdkChecker;

  setWebViewClient(currentWebViewClient);
  setWebChromeClient(currentWebChromeClient);
}

另一处用到 WebViewFlutterApiImpl 的是 WebChromeClientFlutterApiImpl 和 WebViewClientFlutterApiImpl,他们是与 Android WebView 配合工作的 Java Android 类,在这两个类的事件相应方法中,都会先调用 create 方法,确保在 Dart 侧创建有映射实例。

举一例,如下:

/** Passes arguments from {@link WebViewClient#onPageStarted} to Dart. */
public void onPageStarted(
    @NonNull WebViewClient webViewClient,
    @NonNull WebView webView,
    @NonNull String urlArg,
    @NonNull Reply<Void> callback) {
  webViewFlutterApi.create(webView, reply -> {});

  final Long webViewIdentifier =
      Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView));
  onPageStarted(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
}

该文分析的逻辑有待完善 #TODO

这篇文章是我初学时,从细节入手,边学边梳理的。

在我学会之后,建立起全局认识后,有了更好的叙事结构。但是我的精力有限,短时间内也许没有精力完善本文。我想,如果你也在对 webview_flutter 进行深入研究,这些细节仍然是有价值的。

也许补充一些图片会更好一些,同样也是精力有限。在 GeneratedAndroidWebView.java 中会生成 WebViewFlutterApi 同名 Java 类,他的 create 内部是一个 Channel 调用,调用上面的 Dart create。

现在看 create 是哪里调用的?


本文作者:Maeiee

本文链接:Flutter webview_flutter pigeon 通信桥实现原理

版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!


喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!