ToB企服应用市场:ToB评测及商务社交产业平台

标题: Flutter WebView使用以及分析 [打印本页]

作者: 万有斥力    时间: 2022-6-25 20:05
标题: Flutter WebView使用以及分析
Flutter WebView使用以及分析

一、背景

在开发过程中很多时候都需要用WebView展示网页,在android中可以直接使用WebView控件加载网页,iOS也有WKWebView或UIWebView,那么在flutter中如何加载网页?
从以下问题入手:

二、Flutter WebView

flutter层不支持webview,加载网页的功能还需要借助原生控件来处理。
由于Webview是一个非常复杂的控件,flutter再重新实现一遍成本非常高,而各个平台都有很完善的WebView控件,故flutter团队提供了嵌入原生WebView的解决方案,flutter通过 PlatformView 使用原生控件。
通过pub.dev搜索以及对比网上文章,发现了几个比较受欢迎的flutter webview插件,

三个插件对比:

对比总结:

三、使用

下文将使用flutter_inappwebview,进行使用简介,文档地址
flutter_inappwebview主要功能清单如下:

可以看出,flutter_inappwebview支撑多种WebView的使用方式,下文以 InAppWebView 为例进行介绍,因为InAppWebView是Flutter组件可以嵌入Widget Tree,用法更灵活,更贴近实际需求。
Flutter 支持两种模式集成原生控制:虚拟显示模式 (Virtual displays) 和混合集成模式 (Hybrid composition) :

根据具体情况来决定使用哪种模式。flutter webview在iOS平台仅支持Hybrid Composition 模式。
推荐使用Hybrid Composition 模式。
3.1 引入依赖

flutter项目配置文件pubspec.yaml中引入依赖:
  1. dependencies:  
  2.   flutter_inappwebview: ^5.3.2
复制代码
android项目配置:
  1. android {
  2.     compileSdkVersion 29
  3.    
  4.     defaultConfig {
  5.         minSdkVersion 19
  6.         targetSdkVersion 29
  7.     }
  8. }
复制代码
gradle相应版本:
   gradle插件版本 :4.1.* 以及上
  gradle版本 :5.6 及以上
  注意事项:
   flutter_inappwebview需要依赖androidx和swift。
  3.2 flutter中使用

widget中使用,并开启Hybrid Composition模式。
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_inappwebview/flutter_inappwebview.dart';
  3. class Browser extends StatefulWidget {
  4.   const Browser(Key key, this.url, this.title) : super(key: key);
  5.   final String url;
  6.   final String title;
  7.   @override
  8.   _BrowserState createState() => _BrowserState();
  9. }
  10. class _BrowserState extends State<Browser> {
  11.   final GlobalKey webViewKey = GlobalKey();
  12.   InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
  13.     crossPlatform: InAppWebViewOptions(
  14.       useShouldOverrideUrlLoading: true,
  15.       mediaPlaybackRequiresUserGesture: false,
  16.     ),
  17.     /// android 支持HybridComposition
  18.     android: AndroidInAppWebViewOptions(
  19.       useHybridComposition: true,
  20.     ),
  21.     ios: IOSInAppWebViewOptions(
  22.       allowsInlineMediaPlayback: true,
  23.     ),
  24.   );
  25.   @override
  26.   Widget build(BuildContext context) {
  27.     return Scaffold(
  28.       appBar: AppBar(
  29.         title: Text(widget.title),
  30.       ),
  31.       body: InAppWebView(
  32.         key: webViewKey,
  33.         initialUrlRequest: URLRequest(url: Uri.parse(widget.url)),
  34.         initialOptions: options,
  35.       ),
  36.     );
  37.   }
  38. }
复制代码
如果只加载网页不要复杂的定制,这样的配置已经可以啦~
3.3 InAppWebview提供的接口

InAppWebview实现了原生WebView(Android)和WKWebView(iOS)中绝大多数接口,将这些原生接口代理到了Flutter层,极大的提高了不同平台的使用效率。
接口组织方式分为两部分:

在使用某个接口功能时,需要先检测相应接口开关是否开启,开启有对应接口回调才会生效,一些常见的生命周期回调函数则不需要配置开关。
因为不同平台本身的差异,整体接口分为三类,通过InAppWebViewGroupOptions类进行配置管理:

接口配置实例:
  1. InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
  2.     //夸平台配置
  3.     crossPlatform: InAppWebViewOptions(
  4.       useShouldOverrideUrlLoading: true,//加载url拦截功能
  5.       useShouldInterceptAjaxRequest: true,//ajax请求拦截
  6.       useOnLoadResource: true,//资源加载回调
  7.       allowFileAccessFromFileURLs: true,//资源加载
  8.       mediaPlaybackRequiresUserGesture: false,//多媒体控制
  9.     ),
  10.     //Android平台配置
  11.     android: AndroidInAppWebViewOptions(
  12.       useHybridComposition: true,//支持HybridComposition
  13.       useShouldInterceptRequest: true,//请求加载链接,可以用于实现Web离线包
  14.     ),
  15.     //iOS平台配置
  16.     ios: IOSInAppWebViewOptions(
  17.       allowsInlineMediaPlayback: true,
  18.     ),
  19.   );
  20.   
  21.   // 使用options配置
  22.   InAppWebView(
  23.         key: webViewKey,
  24.         initialUrlRequest: URLRequest(url: Uri.parse("https://www.baidu.com/")),
  25.         initialOptions: options,//配置options
  26.         // ...
  27.         )
复制代码
接口回调实例:
  1. InAppWebView(
  2.    key: webViewKey,
  3.    initialUrlRequest: URLRequest(url: Uri.parse("https://www.baidu.com/")),
  4.    initialOptions: options,// 开关配置项
  5.    onWebViewCreated: (InAppWebViewController controller) {
  6.       print("$TAG onWebViewCreated");
  7.    },
  8.    onLoadStart: (InAppWebViewController controller, Uri? url) {
  9.       print("$TAG onLoadStart url:$url");
  10.    },
  11.    onLoadStop: (InAppWebViewController controller, Uri? url) {
  12.       print("$TAG onLoadStop url:$url");
  13.    },
  14.    onLoadError: (InAppWebViewController controller, Uri? url, int code,
  15.             String message) {
  16.       print("$TAG onLoadError url:$url code:$code message:$message");
  17.    },
  18.    onLoadHttpError: (InAppWebViewController controller, Uri? url,
  19.             int statusCode, String description) {
  20.       print("$TAG onLoadHttpError url:$url statusCode:$statusCode                                                     description:$description");
  21.    },
  22.    onConsoleMessage:
  23.         (InAppWebViewController controller, ConsoleMessage consoleMessage) {
  24.           print("$TAG onConsoleMessage consoleMessage:$consoleMessage");
  25.    },
  26.    onProgressChanged: (InAppWebViewController controller, int progress) {
  27.       print("$TAG onProgressChanged progress:$progress");
  28.    },
  29.   shouldOverrideUrlLoading: (InAppWebViewController controller,
  30.             NavigationAction navigationAction) async {
  31.       print("$TAG shouldOverrideUrlLoading navigationAction:$navigationAction");
  32.           return null;
  33.   },
  34.   // 资源加载监听器
  35.   onLoadResource:(InAppWebViewController controller, LoadedResource resource) {
  36.       print("$TAG onLoadResource resource:$resource");
  37.   },
  38.   // 滚动监听器
  39.   onScrollChanged: (InAppWebViewController controller, int x, int y) {
  40.       print("$TAG onScrollChanged x:$x  y:$y");
  41.   },
  42.   onLoadResourceCustomScheme:(InAppWebViewController controller, Uri url) async {
  43.       print("$TAG onLoadResourceCustomScheme url:$url");
  44.       return null;
  45.   },
  46.   onCreateWindow: (InAppWebViewController controller,
  47.             CreateWindowAction createWindowAction) async {
  48.       print("$TAG onCreateWindow");
  49.       return true;
  50.   },
  51.   onCloseWindow: (InAppWebViewController controller) {
  52.       print("$TAG onCloseWindow");
  53.   },
  54.   // 过量滚动监听器
  55.   onOverScrolled: (InAppWebViewController controller, int x, int y,
  56.             bool clampedX, bool clampedY) async {
  57.       print("$TAG onOverScrolled x:$x  y:$y clampedX:$clampedX clampedY:$clampedY");
  58.   },
  59.    
  60.   //Android特有功能,请求加载链接,可以拦截资源加载,并替换为本地Web离线包内的资源
  61.   androidShouldInterceptRequest: (InAppWebViewController controller,
  62.             WebResourceRequest request) async {
  63.       print("$TAG androidShouldInterceptRequest request:$request");
  64.      return null;
  65.   },
  66.    
  67.   //iOS特有功能
  68.   iosOnNavigationResponse: (InAppWebViewController controller,
  69.             IOSWKNavigationResponse navigationResponse) async {
  70.       return null;
  71.   },
  72. )
复制代码
接口名称已经可以表示相应功能了,就不一一加注释了~
3.4 InAppWebview与Js通信

InAppWebview与Js通信有多种方式,具体文档地址
a.获取InAppWebViewController

js通信都是通过 InAppWebViewController 实现的,需要先通过onWebViewCreated方法获取InAppWebViewController 对象:
  1. // InAppWebview中获取InAppWebViewController
  2. onWebViewCreated: (InAppWebViewController controller) {
  3.   // ...
  4. },
复制代码
b.js调Flutter

通过InAppWebViewController#addJavaScriptHandler添加js处理器,js的调用将会回调改方法。
  1. // InAppWebview中获取InAppWebViewController
  2. onWebViewCreated: (InAppWebViewController controller) {
  3.   // 注册一个JS处理方法,名称为myHandlerName
  4.   controller.addJavaScriptHandler(handlerName: 'myHandlerName', callback: (args) {
  5.     // 打印js方传递过来的参数
  6.     print(args);
  7.     // 返回给js方的结果
  8.     return {
  9.       'bar': 'bar_value', 'baz': 'baz_value'
  10.     };
  11.   });
  12. },
复制代码
js代码中:
  1. window.flutter_inappwebview.callHandler('myHandlerName', {a:1,b:2})
复制代码
flutter_inappwebview为InAppWebView框架自动注入的js对象,方便分发js调用到Flutter。
c.Flutter调js

通过 InAppWebViewController.evaluateJavascript 调用js方法。在使用之前需要和js放约定好通讯协议:

  1. onLoadStop: (controller, url) async {
  2.   // 直接调用js代码,并等待结果
  3.   final result = await controller.evaluateJavascript(source: """
  4.     window.handleFlutterInvoke("{a:1,b:2}");
  5.   """);
  6.   print("result")
  7. },
复制代码
其他方式参考上述文档~
四、InAppWebview原理

Flutter的WebView功能是直接依赖不同平台的WebView实现的,已经知道是通过PlatformView实现的,那他们是怎么组合起来的呢?此处以Android平台为例分析下实现过程。
4.1 Flutter与Android组件关联方式

主要实现步骤:

  1. class CustomFlutterWidget extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4.    final defaultTargetPlatform = ...;
  5.      // Android
  6.    if (defaultTargetPlatform == TargetPlatform.android) {
  7.      if (useHybridComposition) {// 混合模式
  8.        return PlatformViewLink(
  9.          viewType: 'viewTypeId',//唯一标识
  10.          // ...
  11.        );
  12.      } else {
  13.        return AndroidView(// 虚拟显示
  14.          viewType: 'viewTypeId',//唯一标识
  15.          // ...
  16.        );
  17.      }
  18.    } else if (defaultTargetPlatform == TargetPlatform.iOS) {
  19.        // iOS
  20.      return UiKitView(// 混合模式
  21.        viewType: 'viewTypeId',//唯一标识
  22.        // ...
  23.      );
  24.    }
  25.    return Text(
  26.        '$defaultTargetPlatform is not yet supported by the flutter_inappwebview plugin');
  27. }
  28. }
复制代码
通过上述步骤就可以使用特定平台的原生View了,iOS也是同样方式实现细节略有差异~
4.2 InAppWebView实现类图


整体类图还是比较清晰的,Flutter与平台层通信借助MethodChannel~
4.3 Flutter与平台层通信

Flutter提供了多种通信渠道的实现方式,具体文档链接


Flutter MethodChannel

  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. ...
  5. class _MyHomePageState extends State<MyHomePage> {
  6.   static const platform = const MethodChannel('samples.flutter.io/testMethodChannel');
  7.   // 调用通信方法.
  8.   Future<Null> _getBatteryLevel() async {
  9.     try {
  10.       final int result = await platform.invokeMethod('methodName', params);
  11.       print(result);
  12.     } on PlatformException catch (e) {
  13.       
  14.     }
  15.   }
  16. }
复制代码
Android MethodChannel

  1. import android.os.Bundle
  2. import io.flutter.app.FlutterActivity
  3. import io.flutter.plugin.common.MethodChannel
  4. import io.flutter.plugins.GeneratedPluginRegistrant
  5. class MainActivity() : FlutterActivity() {
  6.   private val CHANNEL = "samples.flutter.io/testMethodChannel"
  7.   override fun onCreate(savedInstanceState: Bundle?) {
  8.     super.onCreate(savedInstanceState)
  9.     GeneratedPluginRegistrant.registerWith(this)
  10.     MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
  11.       if(call.methodName == 'methodName') {
  12.           Log.d(TAG,"${call.arguments}")
  13.           // TODO
  14.           result.success(1)
  15.       }                                                      
  16.     }
  17.   }
  18. }
复制代码
五、总结

Q1:flutter中是否有类似原生的WebView控件?
A1:Flutter没有类似WebView控件,借助平台层实现WebView功能。
Q2:flutter中如何使用WebView加载网页?
A2:借助现网提供的WebView插件即可实现网络加载,其中flutter_inappwebview插件非常优秀,推荐使用。
Q3:flutter的WebView如何与js通信的?
A3:InAppWebView已经实现了一套完整的js通信机制,如果用官方WebView插件,则需要自己实现一套JsBridge同时适用Android和iOS,成本稍高一点。
Q4:flutter中WebView是如何实现?
A4:Flutter本身不提供WebView功能,通过PlatformView去适用各个平台已有的WebView能力,降低了实现成功。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4