基于 WKWebView 的官方 iOS WebView 插件:webview_flutter_wkwebview 深度指南
【免费下载链接】pluginsPlugins for Flutter maintained by the Flutter team项目地址: https://gitcode.com/gh_mirrors/pl/plugins
webview_flutter_wkwebview是 Flutter 官方插件webview_flutter在 Apple 平台(iOS / macOS)上的 WKWebView 实现,属于官方联邦插件(federated plugin)中的平台实现包。本文以该包根目录的 README.md 为骨架,结合仓库源码与 iOS 原生类,系统讲解它在应用中的接入方式、endorsed 机制背后的注册原理、面向原生代码的 External Native API 用法,以及基于 pigeon/mockito 的二次开发与贡献流程,帮助你既能"开箱即用",也能在需要时深入到原生WKWebView层做定制。
一、包定位:Apple 平台上的 WKWebView 实现
根据 pubspec.yaml 的描述,这是一个"基于 Apple 的 WKWebView 控件提供 WebView Widget 的 Flutter 插件"。它并不是一个独立可用的包,而是webview_flutter在 iOS/macOS 上的官方平台实现:
- 版本:3.1.0;
- SDK 约束:
sdk: ">=2.17.0 <3.0.0"、flutter: ">=3.0.0"; - 插件声明:
implements: webview_flutter,ios.pluginClass为FLTWebViewFlutterPlugin,dartPluginClass为WebKitWebViewPlatform; - 依赖:
webview_flutter_platform_interface ^2.0.0(平台接口抽象层)与path ^1.8.0。
说明:当前仓库中的
webview_flutter生态采用"主包(app-facing API)+ 平台接口包(抽象层)+ 各平台实现包"的联邦插件架构。本包即为抽象层WebViewPlatform在 Apple 平台上的具体实现。
二、接入方式:endorsed 插件与零配置使用
README 明确指出:该包是 endorsed(官方背书)插件,因此应用开发者无需直接依赖它,只需要正常使用webview_flutter主包即可:
dependencies: webview_flutter: ^4.0.0 # 以实际发布的版本为准当应用依赖了webview_flutter后,本包会被自动引入,无需在pubspec.yaml中显式声明,也无需任何额外初始化代码——这正是 endorsed 联邦插件机制带来的体验:平台实现包通过implements: webview_flutter声明自己归属的接口包,由 Flutter 工具链在构建时自动按平台挑选正确的实现。
2.1 背后的注册机制:WebKitWebViewPlatform
从源码层面看,"零配置"的实质是平台实现包在初始化时向平台接口注册自己。核心入口位于 lib/src/webkit_webview_platform.dart:
/// Implementation of [WebViewPlatform] using the WebKit API. class WebKitWebViewPlatform extends WebViewPlatform { /// Registers this class as the default instance of [WebViewPlatform]. static void registerWith() { WebViewPlatform.instance = WebKitWebViewPlatform(); } @override WebKitWebViewController createPlatformWebViewController( PlatformWebViewControllerCreationParams params, ) => WebKitWebViewController(params); @override WebKitNavigationDelegate createPlatformNavigationDelegate( PlatformNavigationDelegateCreationParams params, ) => WebKitNavigationDelegate(params); @override WebKitWebViewWidget createPlatformWebViewWidget( PlatformWebViewWidgetCreationParams params, ) => WebKitWebViewWidget(params); @override WebKitWebViewCookieManager createPlatformCookieManager( PlatformWebViewCookieManagerCreationParams params, ) => WebKitWebViewCookieManager(params); }registerWith()将WebKitWebViewPlatform设为WebViewPlatform.instance全局默认实现,之后主包创建的任何 WebView 组件(控制器、导航委托、Widget、Cookie 管理器)都会经由这里的工厂方法落到 WebKit 原生实现上。该类的对外导出集中在 lib/webview_flutter_wkwebview.dart,它同时导出了WebKitWebViewController、WebKitWebViewCookieManager与WebKitWebViewPlatform三个公开符号。
三、External Native API:在 iOS 原生代码中访问 WKWebView
README 中的核心技术亮点是插件提供的面向原生代码的外部 API(External Native API)。常规情况下,Flutter 开发者只通过 Dart API 与 WebView 交互;但当你的 App 或插件需要在 iOS 原生层对 WKWebView 做深度定制(例如叠加原生手势、接入原生广告 SDK、与原生 JS 桥接)时,可以通过这套 API 拿到真实的WKWebView实例。
3.1 访问约定与破坏性变更承诺
该 API 遵循一个重要的兼容性约定(README 原文要点):
- 外部 API 与 Dart API 共享破坏性变更(breaking change)约定:任何不向后兼容的类变更,只会伴随插件major 版本升级而发生;
- 除外部 API 之外的其余原生代码不遵循破坏性变更约定,因此 App 或插件客户端不应使用任何其他原生 API。
换句话说,仓库ios/Classes/下绝大多数类(各*HostApi、FWFInstanceManager等)都属于内部实现,随时可能变化;只有FWFWebViewFlutterWKWebViewExternalAPI是受版本约定保护的公开契约。
3.2 导入方式与核心类
在 Objective-C 代码中通过模块导入即可:
@import webview_flutter_wkwebview;导入后即可访问原生类FWFWebViewFlutterWKWebViewExternalAPI。该类声明于 ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h,核心方法如下:
@interface FWFWebViewFlutterWKWebViewExternalAPI : NSObject /** * Retrieves the `WKWebView` that is associated with `identifier`. * * @param identifier The associated identifier of the `WebView`. * @param registry The plugin registry the `FLTWebViewFlutterPlugin` should belong to. * @return The `WKWebView` associated with `identifier` or nil if not found. */ + (nullable WKWebView *)webViewForIdentifier:(long)identifier withPluginRegistry:(id<FlutterPluginRegistry>)registry; @end参数含义:
| 参数 | 类型 | 说明 |
|---|---|---|
identifier | long | 与底层 WKWebView 关联的标识符,可在 Dart 侧通过WebKitWebViewController.webViewIdentifier获取 |
registry | id<FlutterPluginRegistry> | 插件注册表;若其中未挂载FLTWebViewFlutterPlugin实例,方法返回nil |
| 返回值 | WKWebView *(可空) | 与identifier关联的 WKWebView;找不到时返回nil |
3.3 Dart 侧如何拿到 identifier
在 Flutter/Dart 侧,先创建WebKitWebViewController,然后通过其webViewIdentifiergetter 拿到标识符。该 getter 定义于 lib/src/webkit_webview_controller.dart:
/// Identifier used to retrieve the underlying native `WKWebView`. /// /// This is typically used by other plugins to retrieve the native `WKWebView` /// from an `FWFInstanceManager`. int get webViewIdentifier => _webKitParams._instanceManager.getIdentifier(_webView)!;结合原生实现(ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.m),完整的调用链为:
- Dart 侧
WebKitWebViewController通过InstanceManager为底层WKWebView分配一个递增的整型 identifier; webViewIdentifier将该 identifier 暴露给上层调用者;- 原生侧
FWFWebViewFlutterWKWebViewExternalAPI.webViewForIdentifier:withPluginRegistry:从插件注册表取出FWFInstanceManager,用同一 identifier 反查原生对象:FWFInstanceManager *instanceManager = (FWFInstanceManager *)[registry valuePublishedByPlugin:@"FLTWebViewFlutterPlugin"]; id instance = [instanceManager instanceForIdentifier:identifier]; if ([instance isKindOfClass:[WKWebView class]]) { return instance; } return nil; - 校验实例类型为
WKWebView后返回给原生调用方。
3.4 典型应用场景示例
假设你需要在 iOS 原生层给 WebView 注入一段自定义 WKUserScript,大致流程如下:
// 1. 从 Dart 侧通过 MethodChannel/EventChannel 把 identifier 传递到原生层 long identifier = ...; // 来自 WebKitWebViewController.webViewIdentifier // 2. 通过 External Native API 获取底层 WKWebView WKWebView *webView = [FWFWebViewFlutterWKWebViewExternalAPI webViewForIdentifier:identifier withPluginRegistry:self.registrar]; if (webView == nil) { // 插件尚未注册或 identifier 无效 return; } // 3. 直接操作原生 WKWebView(示例:注入用户脚本) WKUserScript *script = [[WKUserScript alloc] initWithSource:@"..." injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; [webView.configuration.userContentController addUserScript:script];需要说明的是:该特性自3.1.0版本引入(见 CHANGELOG.md 首条 "Adds support to access nativeWKWebView"),使用时请确认你的依赖解析到了该版本及以上。
四、源码结构:从 Dart 到原生 Host API
理解本包源码布局,有助于评估哪些内容属于受保护的外部 API,哪些属于随时可能变化的内部实现。仓库目录结构如下:
packages/webview_flutter/webview_flutter_wkwebview/ ├── lib/ # Dart 侧实现 │ ├── webview_flutter_wkwebview.dart # 公开导出 │ └── src/ │ ├── common/ # InstanceManager、pigeon 生成的 g.dart │ ├── foundation/ # Foundation 框架 API 实现 │ ├── ui_kit/ # UIKit 框架 API 实现 │ ├── web_kit/ # WebKit API 实现 │ └── webkit_*.dart # 平台实现(controller/cookie_manager/widget) ├── ios/Classes/ # 原生实现(全部 .h/.m) ├── pigeons/web_kit.dart # pigeon 通信接口定义(通信层唯一真源) ├── example/ # 示例工程(含 RunnerTests 原生单测) └── test/ # Dart 单测(含 mockito 生成的 .mocks.dart)ios/Classes/下的原生类大致分三类:
- 公开外部 API:
FWFWebViewFlutterWKWebViewExternalAPI(唯一受版本约定保护); - pigeon 生成的 Host API:
FWFWebViewHostApi、FWFNavigationDelegateHostApi、FWFWebViewConfigurationHostApi、FWFHTTPCookieStoreHostApi、FWFWebsiteDataStoreHostApi、FWFUserContentControllerHostApi、FWFScriptMessageHandlerHostApi、FWFPreferencesHostApi、FWFScrollViewHostApi、FWFUIViewHostApi、FWFUIDelegateHostApi等,均由pigeons/web_kit.dart生成(见 FWFGeneratedWebKitApis.h); - 基础设施:
FWFInstanceManager(原生对象与 identifier 的双向映射,见 FWFInstanceManager.h)、FWFDataConverters、插件入口FLTWebViewFlutterPlugin。
五、二次开发与贡献:pigeon 与 mockito 代码生成流程
README 的 Contributing 部分说明了本包的两条关键开发工作流,对想要修改通信层或为包贡献代码的开发者非常实用。
5.1 修改通信接口后重新生成 pigeon 代码
本包使用 pigeon),Dart 侧的 lib/src/common/web_kit.g.dart 与原生侧的FWFGeneratedWebKitApis.h/.m都是由它生成的产物。
编辑完通信接口后,执行以下命令重新生成通信层:
flutter pub run pigeon --input pigeons/web_kit.dart注:当前仓库的开发依赖锁定 pigeon
^4.2.13(见 pubspec.yaml 的dev_dependencies),命令在包根目录下运行。修改接口后,一般还需要同步更新test/src/common/test_web_kit.g.dart对应的测试桩,可参照 test/src/common/instance_manager_test.dart 等既有测试的写法。
5.2 重新生成 mockito 测试替身
除 pigeon 外,本包还使用 mockito 为测试生成 mock 对象。修改了需要被 mock 的类(如WebKitProxy、各 delegate)后,运行:
flutter pub run build_runner build --delete-conflicting-outputs生成结果即test/目录下的*.mocks.dart文件,例如 test/webkit_webview_controller_test.mocks.dart 与 test/webkit_navigation_delegate_test.mocks.dart。测试用例分布可参考:
- test/webkit_webview_controller_test.dart:控制器行为;
- test/webkit_webview_cookie_manager_test.dart:Cookie 管理;
- test/webkit_webview_widget_test.dart:Widget 组装;
- test/src/foundation/foundation_test.dart、test/src/ui_kit/ui_kit_test.dart、test/src/web_kit/web_kit_test.dart:分层 API 实现。
原生侧还有对应的 Objective-C 单元测试,位于 example/ios/RunnerTests/(如FWFNavigationDelegateHostApiTests.m、FWFWebViewConfigurationHostApiTests.m),可直接验证各 Host API 与FWFInstanceManager的 identifier 传递行为。
5.3 原生侧工程信息
若需要集成或排查原生问题,可参考 podspec ios/webview_flutter_wkwebview.podspec 的关键配置:
source_files/public_header_files:Classes/**/*.{h,m},全部原生源码与公开头文件;module_map:Classes/FlutterWebView.modulemap,支撑@import webview_flutter_wkwebview;的模块化导入;platform:iOS 9.0 起;pod_target_xcconfig:开启DEFINES_MODULE,并排除模拟器 i386 架构。
六、小结
webview_flutter_wkwebview是一个典型的 endorsed 联邦插件:应用侧零配置即可在 iOS/macOS 上获得基于 WKWebView 的完整 WebView 能力;而通过受版本约定保护的FWFWebViewFlutterWKWebViewExternalAPI,原生开发者还能安全地拿到底层WKWebView实例做深度定制。同时,其基于 pigeon 的通信层设计与 mockito 驱动的测试体系,也为维护者和贡献者提供了清晰的二次开发路径。若需了解 Dart 侧主包 API 的完整用法,可继续阅读 webview_flutter 包文档;若需自定义其他平台的实现,可参考同仓库下的webview_flutter_android与webview_flutter_web等兄弟包。
【免费下载链接】pluginsPlugins for Flutter maintained by the Flutter team项目地址: https://gitcode.com/gh_mirrors/pl/plugins
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考