WebView的使用
12147初始化
//定义webView
//代码来源:/jianghuAppBrowser/jianghuBrowser/lib/common/RenderKey.dart
static ValueNotifier<WebViewWidget?> webView = ValueNotifier(null);
//初始化webView
//代码来源:/jianghuAppBrowser/jianghuBrowser/lib/layout/JianghuConfigHandler.dart
Future<void> initWebView({Function? onWebViewCreated}) async {
print("初始化 initWebView");
if (RenderKey.controller.value != null) {
if (onWebViewCreated != null) {
onWebViewCreated();
}
} else {
RenderKey.webView.value = WebViewWidget(
onProgress: (progress) {
RenderKey.loadingLabel.value = '正在加载$progress%';
},
onWebViewCreated: () {
if (onWebViewCreated != null) {
onWebViewCreated();
}
},
onPageLoadEnd: () async {
},
);
}
}
//监听webView,如果webView已经初始化了,则渲染。
//代码来源:/jianghuAppBrowser/jianghuBrowser/lib/layout/JianghuLayout.dart
return ValueListenableBuilder(
valueListenable: RenderKey.webView,
builder: (context, WebViewWidget? webView, _) {
return webView ?? Container();
},
);
配置
基本配置
//代码来源:/jianghuAppBrowser/jianghuBrowser/lib/layout/WebViewWidget.dart
webViewWidget(context) {
return ValueListenableBuilder(
valueListenable: Constants.appId,
builder: (context, appId, _) {
print("=== appId ValueListenableBuilder: $appId");
return WebView(
javascriptMode: JavascriptMode.unrestricted,
appId: appId,
allowFileAccess: true,
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
initialCookies: AuthUtil.cookieName == null ? [] : [WebViewCookie(name: AuthUtil.cookieName!, value: AuthUtil.cookieValue!, domain: AuthUtil.cookieDomain!)],
javascriptChannels: <JavascriptChannel>{
WebViewHandler().messagePost(context),
},
gestureNavigationEnabled: true,
debuggingEnabled: true,
);
},
);
}
- 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数据:
final List<String> initialCookies = [
'session_id=abc123',
'auth_token=xyz987',
'user_id=12345',
];
在构造函数中添加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的导航行为,并以此来实现更好的用户体验。
//代码来源:/jianghuAppBrowser/jianghuBrowser/lib/layout/WebViewWidget.dart
webViewWidget(context) {
return ValueListenableBuilder(
valueListenable: Constants.appId,
builder: (context, appId, _) {
print("=== appId ValueListenableBuilder: $appId");
return WebView(
navigationDelegate: (NavigationRequest request) async {
debugPrint('navigationDelegate ${request.url}');
if (request.url.toLowerCase().startsWith('blob')) {
//拦截了blob请求,不做处理
return NavigationDecision.prevent;
}
String fileName = request.url.substring(request.url.lastIndexOf('/') + 1);
// 获取连接返回类型
String? contentType = await Utils.getUrlResType(request.url);
debugPrint('navigationDelegate.contentType $contentType');
if (contentType != null && contentType.startsWith('application')) {
runDownload(context, request, fileName);
return NavigationDecision.prevent;
}
RenderKey.bottomNavBarRenderHash.value = DateTime.now().millisecondsSinceEpoch;
debugPrint('navigationDelegate.navigate ${request.url}');
//会继续往下走
return NavigationDecision.navigate;
},
);
},
);
}
事件处理
//代码来源:/jianghuAppBrowser/jianghuBrowser/lib/layout/WebViewWidget.dart
webViewWidget(context) {
return ValueListenableBuilder(
valueListenable: Constants.appId,
builder: (context, appId, _) {
print("=== appId ValueListenableBuilder: $appId");
return WebView(
onProgress: (int progress) async {
debugPrint('onProgress $progress');
RenderKey.webUrlLabel.value = await WebViewHandler().getPageTitle();
RenderKey.pageLoadingProgress.value = progress;
widget.onProgress(progress);
},
onWebViewCreated: (WebViewController webViewController) {
Constants.webViewCreated = true;
RenderKey.controller.value = webViewController;
widget.onWebViewCreated();
},
onWebResourceError: (WebResourceError error) {
DioUtil().webViewRequestError.value = error;
debugPrint('onWebResourceError ${error.errorCode} ${error.errorCode} ${error.errorType} ${error.domain} ${error.description} ${error.failingUrl}');
},
onPageStarted: (String url) async {
WebViewHandler().webAppInitialization();
DioUtil().webViewRequestError.value = null;
AuthUtil.saveCookie();
},
onPageFinished: (String url) async {
await onPageFinished(url);
},
);
},
);
}
- 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获取,提升速度。
//代码来源:/jianghuAppBrowser/jianghu_packages/webView/webview_flutter_android-2.8.14/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
long times = System.currentTimeMillis();
if (request == null) {
return super.shouldInterceptRequest(view, (WebResourceRequest) null);
}
String url = request.getUrl().toString();
Log.d(this.getClass().getName(), appId + ", 请求连接::" + url);
url = url.split("://")[0] + "://" + url.split("://")[1].replaceAll("\\/\\/", "/");
boolean isFind = matchUrl(url);
if (isFind) {
initAssetsLoader(request.getUrl());
try {
@Nullable WebResourceResponse result = null;
assert assetLoader != null;
result = assetLoader.shouldInterceptRequest(Uri.parse(url));
if(result == null || result.getMimeType() == null && result.getData() == null) {
Log.d(this.getClass().getName(), "拦截返回本地文件:" + url + " 失败,use: " + (System.currentTimeMillis() - times) + "ms");
result = super.shouldInterceptRequest(view, request);
} else {
Log.d(this.getClass().getName(), "拦截返回本地文件:" + url + " 成功,use: " + (System.currentTimeMillis() - times) + "ms");
}
return result;
} catch (Exception e) {
Log.d(this.getClass().getName(), "shouldInterceptRequest: localPath Exception" + e.getMessage());
return super.shouldInterceptRequest(view, request);
}
} else {
return super.shouldInterceptRequest(view, request);
}
}