一、在原生安卓中的使用
在原生安卓中一般采用的是@JavascriptInterface注解和WebView的addJavascriptInterface方法实现桥接
-
实现一个@JavascriptInterface的注解。
class JavaScriptCall() {
@JavascriptInterface fun CallApp(string: String) { //{"action":"geolocationGet","jsCallBackId":"func1703575966310","params":{"coordinate":1,"withReGeocode":false}} LogUtils.e("CallApp" + string) }}
-
继承WebView使用addJavascriptInterface添加桥接方法。
class BridgeWebView : WebView{
var bridgeJs = JavaScriptCall(this)constructor(context: Context) : super(context) { init() } constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { init() } constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init() } constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { init() } private fun init() { addJavascriptInterface(bridgeJs, "ISANDNative") }}
-
js端调用桥接方法。
// JavaScript代码调用Android原生
ISANDNative.CallApp('{"action":"closeWebView","jsCallBackId":"...","params":{...}}');
二、在flutter端的使用
-
在pubspec.yaml中引用webview的包。
webview_flutter: ^4.0.7
-
创建一个widget实现WebViewController和WebViewWidget(老版的WebView已不建议使用)。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';class UserAgreementWebPage extends StatefulWidget {
final String url;
final String title;const UserAgreementWebPage({Key? key, required this.url, this.title = ''}) : super(key: key);
@override
State<UserAgreementWebPage> createState() => _UserAgreementWebPageState();
}class _UserAgreementWebPageState extends State<UserAgreementWebPage> {
late final WebViewController _controller;
bool _isLoading = true;@override
void initState() {
super.initState();// 初始化WebViewController _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..addJavaScriptChannel( 'ISANDNative', onMessageReceived: (JavaScriptMessage message) { print('接收到JS消息: ${message.message}'); Get.back(); }, ) // 添加额外的错误处理通道,便于调试 ..addJavaScriptChannel( 'debugChannel', onMessageReceived: (JavaScriptMessage message) { print('调试信息: ${message.message}'); }, ) ..setNavigationDelegate( NavigationDelegate( onPageStarted: (String url) { setState(() { _isLoading = true; }); }, onPageFinished: (String url) { setState(() { _isLoading = false; }); }, onWebResourceError: (WebResourceError error) { setState(() { _isLoading = false; }); // 处理错误 print('Web错误: ${error.description}'); }, onNavigationRequest: (NavigationRequest request) async { print("onNavigationRequest:${request.url}"); return NavigationDecision.navigate; }, ), ) ..loadRequest(Uri.parse(widget.url));}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: Colors.white, // 设置状态栏背景色
),
child: SafeArea(
child: Stack(
children: [
WebViewWidget(controller: _controller),
if (_isLoading)
const Center(
child: CircularProgressIndicator(),
),
],
),
),
);
}@override
void dispose() {
// 恢复状态栏的默认样式
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent, // 根据您应用的默认样式设置
statusBarIconBrightness: Brightness.dark, // 根据您应用的默认样式设置
));
super.dispose();
}
}
其中的addJavaScriptChannel即是添加桥接的方法,ISANDNative为通道名称,onMessageReceived中为收到的消息。
- 通过上述配置后会发现实际还是收不到通道的消息,这是因为原生安卓和flutter调用的差异导致的。
Android原生调用方式:
// JavaScript代码调用Android原生
ISANDNative.CallApp('{"action":"closeWebView","jsCallBackId":"...","params":{...}}');
Flutter调用方式:
// JavaScript代码调用Flutter
ISANDNative.postMessage('{"action":"closeWebView","jsCallBackId":"...","params":{...}}');
如果Web端采用了flutter的带哦用方式,我们就可以正常收到通道的消息了。
-
如果您无法修改Web页面的JavaScript代码,可以在Flutter中注入一个适配脚本,将CallApp调用转换为postMessage。完整代码如下:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';class UserAgreementWebPage extends StatefulWidget {
final String url;
final String title;const UserAgreementWebPage({Key? key, required this.url, this.title = ''}) : super(key: key);
@override
State<UserAgreementWebPage> createState() => _UserAgreementWebPageState();
}class _UserAgreementWebPageState extends State<UserAgreementWebPage> {
late final WebViewController _controller;
bool _isLoading = true;@override
void initState() {
super.initState();// 初始化WebViewController _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..addJavaScriptChannel( 'ISANDNative', onMessageReceived: (JavaScriptMessage message) { print('接收到JS消息: ${message.message}'); Get.back(); }, ) // 添加额外的错误处理通道,便于调试 ..addJavaScriptChannel( 'debugChannel', onMessageReceived: (JavaScriptMessage message) { print('调试信息: ${message.message}'); }, ) ..setNavigationDelegate( NavigationDelegate( onPageStarted: (String url) { setState(() { _isLoading = true; }); }, onPageFinished: (String url) { // 注入适配脚本,将CallApp调用转换为postMessage _controller.runJavaScript(''' // 创建一个包装器来捕获CallApp调用 window.ISANDNative_Original = window.ISANDNative; window.ISANDNative = { CallApp: function(jsonString) { // 转换为Flutter的调用方式 window.ISANDNative_Original.postMessage(jsonString); } }; '''); setState(() { _isLoading = false; }); }, onWebResourceError: (WebResourceError error) { setState(() { _isLoading = false; }); // 处理错误 print('Web错误: ${error.description}'); }, onNavigationRequest: (NavigationRequest request) async { print("onNavigationRequest:${request.url}"); return NavigationDecision.navigate; }, ), ) ..loadRequest(Uri.parse(widget.url));}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: Colors.white, // 设置状态栏背景色
),
child: SafeArea(
child: Stack(
children: [
WebViewWidget(controller: _controller),
if (_isLoading)
const Center(
child: CircularProgressIndicator(),
),
],
),
),
);
}@override
void dispose() {
// 恢复状态栏的默认样式
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent, // 根据您应用的默认样式设置
statusBarIconBrightness: Brightness.dark, // 根据您应用的默认样式设置
));
super.dispose();
}
} -
经过上述操作后,就可以在日志打印中看到我们收到的消息了。
