WebView的使用

12147

初始化

  1. //定义webView
  2. //代码来源:/jianghuAppBrowser/jianghuBrowser/lib/common/RenderKey.dart
  3. static ValueNotifier<WebViewWidget?> webView = ValueNotifier(null);
  4. //初始化webView
  5. //代码来源:/jianghuAppBrowser/jianghuBrowser/lib/layout/JianghuConfigHandler.dart
  6. Future<void> initWebView({Function? onWebViewCreated}) async {
  7. print("初始化 initWebView");
  8. if (RenderKey.controller.value != null) {
  9. if (onWebViewCreated != null) {
  10. onWebViewCreated();
  11. }
  12. } else {
  13. RenderKey.webView.value = WebViewWidget(
  14. onProgress: (progress) {
  15. RenderKey.loadingLabel.value = '正在加载$progress%';
  16. },
  17. onWebViewCreated: () {
  18. if (onWebViewCreated != null) {
  19. onWebViewCreated();
  20. }
  21. },
  22. onPageLoadEnd: () async {
  23. },
  24. );
  25. }
  26. }
  27. //监听webView,如果webView已经初始化了,则渲染。
  28. //代码来源:/jianghuAppBrowser/jianghuBrowser/lib/layout/JianghuLayout.dart
  29. return ValueListenableBuilder(
  30. valueListenable: RenderKey.webView,
  31. builder: (context, WebViewWidget? webView, _) {
  32. return webView ?? Container();
  33. },
  34. );

配置

基本配置

  1. //代码来源:/jianghuAppBrowser/jianghuBrowser/lib/layout/WebViewWidget.dart
  2. webViewWidget(context) {
  3. return ValueListenableBuilder(
  4. valueListenable: Constants.appId,
  5. builder: (context, appId, _) {
  6. print("=== appId ValueListenableBuilder: $appId");
  7. return WebView(
  8. javascriptMode: JavascriptMode.unrestricted,
  9. appId: appId,
  10. allowFileAccess: true,
  11. initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
  12. initialCookies: AuthUtil.cookieName == null ? [] : [WebViewCookie(name: AuthUtil.cookieName!, value: AuthUtil.cookieValue!, domain: AuthUtil.cookieDomain!)],
  13. javascriptChannels: <JavascriptChannel>{
  14. WebViewHandler().messagePost(context),
  15. },
  16. gestureNavigationEnabled: true,
  17. debuggingEnabled: true,
  18. );
  19. },
  20. );
  21. }
  • javascriptMode

用于控制WebView是否启用JavaScript执行。这个参数的默认值为JavascriptMode.disabled,表示不允许WebView执行JavaScript代码。

启用JavaScript可以使我们在WebView中执行动态的Web内容,例如更新页面元素、发送异步请求等。当我们需要在WebView中执行JavaScript代码时,我们需要将javascriptMode参数设置为以下三个选项之一:

  • JavascriptMode.disabled:禁用JavaScript执行。
  • JavascriptMode.unrestricted:允许JavaScript执行。这将允许WebView执行来自任何源的JavaScript代码,可能会导致安全问题。
  • JavascriptMode.restricted:允许JavaScript执行,但有一些限制。例如,WebView将只允许JavaScript执行来自WebView的源代码。

需要注意的是,执行JavaScript可能会导致安全问题,因此我们建议在启用JavaScript时保持警觉,并尽可能避免在WebView中加载来自不可信源的内容。

  • debuggingEnabled

用于启用或禁用WebView的调试功能。这个参数的默认值为false,表示禁用调试功能。

启用调试功能可以帮助开发者快速识别和解决WebView中的问题。例如,在WebView中调试JavaScript代码时,我们可以利用浏览器的调试器来定位错误和调试代码。通过启用debuggingEnabled参数,我们可以在WebView中使用类似的调试工具来提高开发效率。
可以使用chrome调试功能,chrome://inspect/#devices

需要注意的是,启用调试模式可能会在WebView中增加一些性能开销,因此我们建议只在必要时启用该模式,并在生产环境中禁用调试模式。

  • gestureNavigationEnabled:

用于启用或禁用WebView的手势导航功能。这个参数的默认值为false,表示手势导航被禁用。

手势导航是指在使用WebView时,通过手势操作可以实现页面的前进、后退和刷新功能。例如,在iOS设备上,用户可以使用滑动手势操作来实现页面的前进和后退。这种手势导航功能能够提供更流畅的用户体验,使用户可以更方便地控制WebView的行为。

当gestureNavigationEnabled参数被设置为true时,手势导航功能将启用。

需要注意的是,根据不同平台和WebView的版本,手势导航的实现方式可能略有不同。因此,在使用手势导航功能时,我们建议进行适当的测试和验证。

  • initialCookies

用于在WebView中设置初始cookie。initialCookies提供了一种更方便的方法来向WebView中传递cookie,而不需要在WebView中设置JavaScript bridge来进行cookie管理。这是因为initialCookies参数是一个用于设置初始cookie的List对象,其中每个元素都是一个字符串,该字符串表示一个Cookie的键值对。

以下是一个使用initialCookies的Mock数据:

  1. final List<String> initialCookies = [
  2. 'session_id=abc123',
  3. 'auth_token=xyz987',
  4. 'user_id=12345',
  5. ];

在构造函数中添加initialCookies参数。在使用WebView时,我们传递initialCookies作为参数,以便在WebView中设置初始cookie。当我们在WebView中加载网页时,初始化的cookie将被自动添加到HTTPHeader中。

  • initialMediaPlaybackPolicy:

用于控制WebView加载媒体资源(例如视频或音频)时的自动播放策略。这个参数的默认值为AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,表示不允许自动播放任何媒体资源。

当WebView加载包含媒体资源的页面时,这个参数允许我们控制WebView在何时启动自动播放。有三个选项可供选择:

  • AutoMediaPlaybackPolicy.always_allow:允许自动播放所有媒体资源。
  • AutoMediaPlaybackPolicy.always_deny:禁止自动播放所有媒体资源。
  • AutoMediaPlaybackPolicy.require_user_action_only:启动媒体资源的自动播放需要用户的手动操作(例如点击媒体元素)。

initialMediaPlaybackPolicy参数使我们能够控制WebView在何时启动自动播放,从而提供更好的用户体验,并确保用户在必要时能够自己控制媒体资源的播放。

  • allowFileAccess

是否允许web端input file 打开文件选择

路由

navigationDelegate是一个重要的控制器,它用于决定WebView导航时的行为。它允许在发生页面导航时执行自定义代码逻辑,比如控制是否允许导航,跳转页面,重定向等。在应用中添加navigationDelegate能够帮助开发者更好地控制WebView的导航行为,并以此来实现更好的用户体验。

  1. //代码来源:/jianghuAppBrowser/jianghuBrowser/lib/layout/WebViewWidget.dart
  2. webViewWidget(context) {
  3. return ValueListenableBuilder(
  4. valueListenable: Constants.appId,
  5. builder: (context, appId, _) {
  6. print("=== appId ValueListenableBuilder: $appId");
  7. return WebView(
  8. navigationDelegate: (NavigationRequest request) async {
  9. debugPrint('navigationDelegate ${request.url}');
  10. if (request.url.toLowerCase().startsWith('blob')) {
  11. //拦截了blob请求,不做处理
  12. return NavigationDecision.prevent;
  13. }
  14. String fileName = request.url.substring(request.url.lastIndexOf('/') + 1);
  15. // 获取连接返回类型
  16. String? contentType = await Utils.getUrlResType(request.url);
  17. debugPrint('navigationDelegate.contentType $contentType');
  18. if (contentType != null && contentType.startsWith('application')) {
  19. runDownload(context, request, fileName);
  20. return NavigationDecision.prevent;
  21. }
  22. RenderKey.bottomNavBarRenderHash.value = DateTime.now().millisecondsSinceEpoch;
  23. debugPrint('navigationDelegate.navigate ${request.url}');
  24. //会继续往下走
  25. return NavigationDecision.navigate;
  26. },
  27. );
  28. },
  29. );
  30. }

事件处理

  1. //代码来源:/jianghuAppBrowser/jianghuBrowser/lib/layout/WebViewWidget.dart
  2. webViewWidget(context) {
  3. return ValueListenableBuilder(
  4. valueListenable: Constants.appId,
  5. builder: (context, appId, _) {
  6. print("=== appId ValueListenableBuilder: $appId");
  7. return WebView(
  8. onProgress: (int progress) async {
  9. debugPrint('onProgress $progress');
  10. RenderKey.webUrlLabel.value = await WebViewHandler().getPageTitle();
  11. RenderKey.pageLoadingProgress.value = progress;
  12. widget.onProgress(progress);
  13. },
  14. onWebViewCreated: (WebViewController webViewController) {
  15. Constants.webViewCreated = true;
  16. RenderKey.controller.value = webViewController;
  17. widget.onWebViewCreated();
  18. },
  19. onWebResourceError: (WebResourceError error) {
  20. DioUtil().webViewRequestError.value = error;
  21. debugPrint('onWebResourceError ${error.errorCode} ${error.errorCode} ${error.errorType} ${error.domain} ${error.description} ${error.failingUrl}');
  22. },
  23. onPageStarted: (String url) async {
  24. WebViewHandler().webAppInitialization();
  25. DioUtil().webViewRequestError.value = null;
  26. AuthUtil.saveCookie();
  27. },
  28. onPageFinished: (String url) async {
  29. await onPageFinished(url);
  30. },
  31. );
  32. },
  33. );
  34. }
  • onWebViewCreated

当我们在应用中创建一个WebView时,我们可以指定一个回调函数来处理WebView创建的事件。在这个回调函数中,我们可以执行一些初始化操作,例如设置WebView的属性、加载本地资源等。

  • onPageStarted

当我们在WebView中加载一个网页时,这个方法会被调用。在这个方法中,我们可以执行一些操作,例如显示一个进度条、更新UI等。

  • onPageFinished

当我们在WebView中加载一个网页时,这个方法会在页面加载完成后被调用。在这个方法中,我们可以执行一些操作,例如隐藏进度条、更新UI等。

  • onProgress

当我们在WebView中加载一个网页时,这个方法会在页面加载过程中被多次调用,每次调用都会传递一个当前加载进度的值。在这个方法中,我们可以更新进度条等UI组件,以反映当前的加载进度。

  • onWebResourceError

这个回调会在WebView加载过程中发生错误时被调用。具体来说,当WebView加载一个页面或一个资源时遇到错误时,此回调函数会被调用。

onWebResourceError的作用在于允许我们在发生错误时执行特定的操作,例如显示一个错误提示,记录错误信息等。这个回调函数有两个参数,分别是错误状态码和错误描述。我们可以使用这些参数来判断错误类型并采取适当的措施。

扩展功能

拦截静态文件请求,如果是静态文件,则从/webView/webview_flutter_android-2.8.14/android/src/main/assets/public下获取对应文件,不从web获取,提升速度。

  1. //代码来源:/jianghuAppBrowser/jianghu_packages/webView/webview_flutter_android-2.8.14/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java
  2. @Override
  3. public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
  4. long times = System.currentTimeMillis();
  5. if (request == null) {
  6. return super.shouldInterceptRequest(view, (WebResourceRequest) null);
  7. }
  8. String url = request.getUrl().toString();
  9. Log.d(this.getClass().getName(), appId + ", 请求连接::" + url);
  10. url = url.split("://")[0] + "://" + url.split("://")[1].replaceAll("\\/\\/", "/");
  11. boolean isFind = matchUrl(url);
  12. if (isFind) {
  13. initAssetsLoader(request.getUrl());
  14. try {
  15. @Nullable WebResourceResponse result = null;
  16. assert assetLoader != null;
  17. result = assetLoader.shouldInterceptRequest(Uri.parse(url));
  18. if(result == null || result.getMimeType() == null && result.getData() == null) {
  19. Log.d(this.getClass().getName(), "拦截返回本地文件:" + url + " 失败,use: " + (System.currentTimeMillis() - times) + "ms");
  20. result = super.shouldInterceptRequest(view, request);
  21. } else {
  22. Log.d(this.getClass().getName(), "拦截返回本地文件:" + url + " 成功,use: " + (System.currentTimeMillis() - times) + "ms");
  23. }
  24. return result;
  25. } catch (Exception e) {
  26. Log.d(this.getClass().getName(), "shouldInterceptRequest: localPath Exception" + e.getMessage());
  27. return super.shouldInterceptRequest(view, request);
  28. }
  29. } else {
  30. return super.shouldInterceptRequest(view, request);
  31. }
  32. }