嗯...很多

Null-safety支持

InAppWebView 5配备了Dart null-safety支持! 这意味着什么?我们知道,Dart的稳定版还没有发布null-safety功能(需要Dart SDK >=2.12.0-0 <3.0.0),所以,要使用这个插件,你还不能使用Flutter稳定版。 你需要升级Flutter,切换频道,比如,切换到dev频道:flutter频道dev,然后,flutter升级(见 切换Flutter频道 )。

支持安卓混合构图

使用新的WebView Android专用选项useHybridComposition: true来启用混合合成。 这将大大改善Android上的WebView的性能,并且,还将解决所有与键盘相关的问题。 需要注意的是,这个选项需要Flutter v1.20以上,并且只能用于Android 10以上的发布应用,因为动画在< Android 10上会掉帧(见 Hybrid-Composition#performance )。

不再将URL作为一个字符串=更少的问题

所有将URL表示为字符串的类属性现在都已转换为Uri类型。为什么会有这样的变化?在某些情况下,由一个简单的String表示的URL可能会有问题,例如,当你在它里面有空格或特殊字符没有正确编码时。

使用Uri.parse()并将String转换为Uri,将为你解决很多URL的问题。

另外,还有一个新的类URLRequest,它代表了,嗯,一个使用Uri作为url属性类型的URL加载请求。

当你想使用loadUrl方法或在新的WebView属性initialUrlRequest中使用这个类(它取代了旧的initialUrl和initialHeaders属性)。一个简单的使用例子是

child: InAppWebView(
  initialUrlRequest: URLRequest(
      url: Uri.parse("https://flutter.dev/")

此外,使用URLRequest ,你可以进行一个初始的POST请求,比如。

child: InAppWebView(
  initialUrlRequest: URLRequest(
    url: Uri.parse("https://example.com"),
    method: 'POST',
    body: Uint8List.fromList(utf8.encode("name=FooBar")),
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'

不幸的是,在Android上,POST请求将忽略headers属性,因为没有任何本地的API来加载带有headers的POST请求,如loadUrl 。这是因为本地方法android.webkit.WebView.postUrl是唯一可以发送这种类型请求的方法。

iOS<11.0版本对Cookies的支持有限

由于WKHTTPCookieStore只在iOS≥11.0的情况下可用,所以通过JavaScript对iOS<11.0的情况下增加了有限的支持,所以,你无法获取、创建或更改HttpOnly cookies。另外,对于session-only cookie,你需要使用新的iosBelow11WebViewController参数(如果你想使用的那个Cookie Manager方法可用)。

什么是UserScript?我们可以说UserScript类相当于WKUserScript的iOS原生类。

当我可以使用evaluateJavascript方法来注入我的JavaScript代码时,我为什么要使用它呢?嗯,是的,但是不对!UserScript为你提供了注入JavaScript代码的可能性。UserScript为您提供了在加载其他资源之前注入JavaScript的可能性,例如,将injectionTime属性设置为UserScriptInjectionTime.at_DOCUMENT_START(相当于WKUserScriptInjectionTime.atDocumentStart iOS原生属性)。

不过,我在这里应该为Android做一个精确的规定。我说的在iOS上是可以保证的,但在Android上就不行了(你知道iOS !=Android吗?🤷♂️)!

这是因为Android端不存在相应的原生类/功能,所以InAppWebView会尝试尽快注入所有的用户脚本。你可以把它想象成这样。

要管理UserScripts,你可以使用相应的方法,比如addUserScript、removeUserScript等。

从iOS 14.0+开始,WebKit引入了WKContentWorlds的概念,它是一个为JavaScript代码定义执行范围的对象,你用它来防止不同脚本之间的冲突。

这个概念也被引入到flutter_inappwebview插件中,新的,你猜对了,ContentWorld类。

但是,就像我之前说的,iOS !=Android,而且,在Android上,这个概念并不原生存在。所以,就用<iframe>HTML元素的用法来实现。

你可能会问,为什么我不使用类似LiquidCore库或者类似JavaScriptCore iOS框架的东西来实现它。

使用这些库/框架的问题是,您当然不能访问当前网页的窗口或文档 JavaScript 对象。

相反,使用 WKContentWorld,您可以访问这些对象,因此,您可以与网页本身进行交互。

在 Android 上使用 iframes,您可以创建一个新的 JavaScript 上下文,而不与网页的主 JavaScript 上下文冲突(例如,您可以拥有 2 个同名的变量,因为它们存在于 2 个不同的上下文/内容世界中),并实现这种内容世界,如在 iOS 上。所以,这个插件会创建一个id属性等于flutter_inappwebview_[Content World Name HERE]的<iframe>并附加到只包含内联脚本的网页内容上,以便为JavaScript代码定义一个新的执行范围。

显然,这有一些限制/缺点。

  • 对于任何ContentWorld,除了ContentWorld.PAGE(即网页本身),如果你需要访问window或document全局对象,你需要使用window.top和window.top.document,因为代码在iframe内运行。
  • 内联脚本的执行可能会被Content-Security-Policy头所阻止。
  • 一个简单的例子。

    child: InAppWebView(
      initialUrlRequest: URLRequest(
        url: Uri.parse("https://flutter.dev"),
      onLoadStop: (controller, url) async {
        await controller.evaluateJavascript(source: "var foo = 49;");
        await controller.evaluateJavascript(source: "var bar = 19;",
            contentWorld: ContentWorld.PAGE);
        print(await controller.evaluateJavascript(source: "foo + bar;"));
        print(await controller.evaluateJavascript(source: "bar;",
            contentWorld: ContentWorld.DEFAULT_CLIENT));
        await controller.evaluateJavascript(source: "var bar = 2;",
            contentWorld: ContentWorld.DEFAULT_CLIENT);
        print(await controller.evaluateJavascript(source: "bar;",
            contentWorld: ContentWorld.DEFAULT_CLIENT));
        if (Platform.isIOS) {
          await controller.evaluateJavascript(
              source: "document.body.innerHTML = 'LOL';",
              contentWorld: ContentWorld.world(name: "MyWorld"));
        } else {
          await controller.evaluateJavascript(
              source: "window.top.document.body.innerHTML = 'LOL';",
              contentWorld: ContentWorld.world(name: "MyWorld"));
      onConsoleMessage: (controller, consoleMessage) {
        print(consoleMessage);
    

    这个例子的证明就留给读者吧。

    苹果支付API

    iOS 13.0+新增了一个WebView选项,以启用JavaScript Apple Pay API:applePayAPIEnabled。正如Safari 13官方发布说明中写的那样,如果使用了任何脚本注入API(如evaluJavascript或UserScript),它将无法工作。

    所以,当这个属性为真时,iOS上所有使用JavaScript实现的方法、选项和事件都不会被调用或不会做任何事情,结果永远是空的,但是,嘿,你会从你的用户那里得到报酬🤑(这很好,不是吗?

    查看flutter_inappwebview官方文档,了解受此影响的API的完整列表!

    评估Async JavaScript代码

    从iOS 14.0+开始,WebKit增加了新的callAsyncJavaScript方法,该方法允许将源代码作为异步JavaScript函数执行(参见MDN - async函数)。这个方法已经在Android和iOS平台上实现。

    在iOS上,你可以从iOS 10.3+开始使用这个函数,因为正如这里所说:async函数 浏览器兼容性,从该版本开始应该已经支持async函数。因此,在iOS版本的[10.3, 14.0)范围内,使用evaluateJavascript方法,并使用包含标识符和回调的Map来实现,回调将在异步函数执行结束时调用返回的结果。在Android上,它也是以同样的方式实现的!

    返回类型与evaluateJavascript方法不同,而是一个CallAsyncJavaScriptResult实例,其中value属性包含成功值(如果有的话),error属性包含一个代表失败值(如果有的话)的String。

    下面是一个例子。

    child: InAppWebView(
      initialUrlRequest: URLRequest(
        url: Uri.parse("https://flutter.dev"),
      onLoadStop: (controller, url) async {
        final String functionBody = """
            var p = new Promise(function (resolve, reject) {
               window.setTimeout(function() {
                 if (x >= 0) {
                   resolve(x);
                 } else {
                   reject(y);
               }, 1000);
            await p;
            return p;
          """;
        var result = await controller.callAsyncJavaScript(
            functionBody: functionBody,
            arguments: {'x': 49, 'y': 'error message'});
        print(result);
        result = await controller.callAsyncJavaScript(
            functionBody: functionBody,
            arguments: {'x': -49, 'y': 'error message'});
        print(result);
    

    这将打印{value.49, error: null}和{value: null, error: error: null}。49, error: null}和{value: null, error: "错误信息"}。

    服务工作者API

    在Android上,AndroidServiceWorkerController和AndroidServiceWorkerClient类可以用来拦截请求。在使用这些类或它们的方法之前,你应该检查你要使用的服务工作者功能是否被支持,例如。

    Future main() async {
      WidgetsFlutterBinding.ensureInitialized();
      if (Platform.isAndroid) {
        await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
        var swAvailable = await AndroidWebViewFeature.isFeatureSupported(AndroidWebViewFeature.SERVICE_WORKER_BASIC_USAGE);
        var swInterceptAvailable = await AndroidWebViewFeature.isFeatureSupported(AndroidWebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST);
        if (swAvailable && swInterceptAvailable) {
          AndroidServiceWorkerController serviceWorkerController = AndroidServiceWorkerController.instance();
          serviceWorkerController.serviceWorkerClient = AndroidServiceWorkerClient(
            shouldInterceptRequest: (request) async {
              print(request);
              return null;
      runApp(MyApp());
    

    相反,在iOS上,从iOS 14.0+开始,JavaScript Service Worker API是可用的。

    要在iOS上启用这个JavaScript API,只有2种方法。

  • 使用 "App-Bound Domains"
  • 您的应用程序建议自己作为一个可能的 "默认浏览器",如iOS Safari或Google Chrome。
  • App-Bound Domains:请阅读 WebKit - App-Bound Domains 一文了解详情。你可以使用新的Info.plist键WKAppBoundDomains来指定最多10个 "应用绑定 "域,例如。

    <key>WKAppBoundDomains</key> <array> <string>flutter.dev</string> <string>github.com</string> </array> </dict>

    之后,你需要将limitsNavigationsToAppBoundDomains iOS特定的WebView选项设置为true,例如。

    InAppWebViewGroupOptions(
      ios: IOSInAppWebViewOptions(
        limitsNavigationsToAppBoundDomains: true
    

    iOS默认浏览器:请阅读准备你的应用程序成为默认浏览器或电子邮件客户端的文章,了解详情。

    网络消息通道和网络消息监听器

    除了JavaScript处理程序之外,还有两种新的方式与JavaScript进行通信。

    Web消息通道:它们是HTML5消息通道的代表。更多细节请参见 Channel Messaging API。要创建Web消息通道,你需要使用InAppWebViewController.createWebMessageChannel方法。该方法应在页面加载时调用,例如,当WebView.onLoadStop事件被触发时。

    Web消息监听器。它允许在WebMessageListener监听的每个框架中注入一个JavaScript对象。要添加一个Web消息监听器,你需要使用InAppWebViewController.addWebMessageListener方法。这个方法应该在使用它的网页被加载之前被调用,例如当WebView.onWebViewCreated事件被触发时。

    这个插件的官方网站已经发布在inappwebview.dev上了!

    所有在repository的README.md上写的初始设置和配置都将被移到那里。我将添加一个入门部分和其他文档来帮助你开始使用它!

    另外,它还包含一个展示部分,有一个用Flutter和Flutter InAppWebView构建的应用程序的开放列表。目前,因为网站是新的,所以只有一个App,那就是Flutter浏览器App

    你是否在使用这个插件?通过提交App页面提交你的App,并按照说明操作吧!

    查看CHANGELOG.md文件,查看更改和修复的完整列表!

    今天就到这里吧!

    我要感谢所有以任何方式支持这个项目的人! 非常感谢大家的支持! 💙