flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge
在使用webview中,需要实现flutter与Javascript交互,在使用webview_flutter插件的时候,整理了一下webview与Javascript的交互JSBridge,具体可以查看
https://blog.csdn.net/gloryFlow/article/details/131683122
这里使用inappwebview插件来实现flutter与Javascript的交互JSBridge。
一、什么是JSBridge
JSBridge是一种实现webview与原生端的相互调用的能力。
在比较流行的JSBridge中,主要是通过拦截URL请求来达到 native 端和 webview 端相互通信的效果的。如WebviewJavascriptBridge。
那在inappwebview中有实现javascript交互的方式。在inappwebview中,可以使用JavaScript Handlers,来实现flutter端与javascript的交互。可以查看
https://blog.csdn.net/gloryFlow/article/details/133643136
二、修改JSBridge的JS端实现
在WebviewJavascriptBridge中,代码中使用iframe中,拦截url来达到webview与原生交互。那在inappwebview,我们可以直接嵌套使用JavaScript Handlers来实现交互。
我们定义WebviewJSBridgeReady
const String kWebviewJsBridgeReady = '''window.onerror = function(err) {log('window.onerror: ' + err)}function setupWebViewJavascriptBridge(callback) {if (window.AppJSBridge) {return callback(AppJSBridge);} else {document.addEventListener('AppJSBridgeReady', function() {callback(AppJSBridge);},false);}}setupWebViewJavascriptBridge(function(bridge) {bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {var responseData = { 'Javascript Says':'Right back atcha!' }responseCallback(responseData)});bridge.registerHandler('JSHandler', function(data, responseCallback) {var responseData = { 'Javascript Says':'Right back atcha!' }responseCallback(responseData)});}
''';
修改后的WebviewJsBridge,其中定义了sendMessageQueue、messageHandlers等。其中定义了一个window.AppJSBridge,创建了事件document.createEvent(‘Event’),初始化event.initEvent(‘AppJSBridgeReady’, true, true); 触发对象dispatch触发对象可以是任何元素或其他事件目标document.dispatchEvent(event);
const String kInAppWebViewJavascriptBridge = '''
function preprocessorJS() {if (window.AppJSBridge) {return;}if (!window.onerror) {window.onerror = function(msg, url, line) {console.log("AppJSBridge: ERROR:" + msg + "@" + url + ":" + line);}}// var messagingIframe;var sendMessageQueue = [];var messageHandlers = {};var responseCallbacks = {};var uniqueId = 1;var dispatchMessagesWithTimeoutSafety = true;function registerHandler(handlerName, handler) {messageHandlers[handlerName] = handler;}function callHandler(handlerName, data, responseCallback) {if (arguments.length == 2 && typeof data == 'function') {responseCallback = data;data = null;}_doSend({ handlerName:handlerName, data:data }, responseCallback);}function call(handlerName, data, responseCallback) {if (arguments.length == 2 && typeof data == 'function') {responseCallback = data;data = null;}_doSend({ handlerName:handlerName, data:data }, responseCallback);}function disableJavscriptAlertBoxSafetyTimeout() {dispatchMessagesWithTimeoutSafety = false;}function _doSend(message, responseCallback) {if (responseCallback) {var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();responseCallbacks[callbackId] = responseCallback;message['callbackId'] = callbackId;}sendMessageQueue.push(message);// 通过使用inappwebview的callHandlerwindow.flutter_inappwebview.callHandler(message['handlerName'], message);}function _fetchQueue() {var messageQueueString = JSON.stringify(sendMessageQueue);sendMessageQueue = [];return messageQueueString;}function _dispatchMessageFromObjC(messageJSON) {if (dispatchMessagesWithTimeoutSafety) {setTimeout(_doDispatchMessageFromObjC);} else {_doDispatchMessageFromObjC();}// 打印log_consoleLog("AppJSBridge: messageJSON:" + messageJSON);function _doDispatchMessageFromObjC() {var message = JSON.parse(messageJSON);var messageHandler;var responseCallback;if (message.responseId) {responseCallback = responseCallbacks[message.responseId];if (!responseCallback) {return;}responseCallback(message.responseData);delete responseCallbacks[message.responseId];} else {if (message.callbackId) {var callbackResponseId = message.callbackId;responseCallback = function(responseData) {_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });};}var handler = messageHandlers[message.handlerName];if (!handler) {_consoleLog("AppJSBridge: WARNING: no handler for message from ObjC:", message);} else {handler(message.data, responseCallback);}}}}function _handleMessageFromObjC(messageJSON) {_dispatchMessageFromObjC(messageJSON);}registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);window.AppJSBridge = {registerHandler: registerHandler,callHandler: callHandler,call: call,disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,_fetchQueue: _fetchQueue,_handleMessageFromObjC: _handleMessageFromObjC,_consoleLog: _consoleLog,};// 打印logfunction _consoleLog(message) {// 显示来自flutter的回调var logJSON = { 'message':message, 'logType':1 }callHandler("log", JSON.stringify(logJSON));}window.WeixinJSBridge = window.AppJSBridge;// 创建事件var event = document.createEvent('Event');// 定义事件名为'build'.event.initEvent('AppJSBridgeReady', true, true);event.bridge = window.AppJSBridge;// 触发对象可以是任何元素或其他事件目标document.dispatchEvent(event);
}function preprocessorReadyJs() {if (!window.flutter_inappwebview.callHandler) {window.flutter_inappwebview.callHandler = function () {var _callHandlerID = setTimeout(function () { });window.flutter_inappwebview._callHandler(arguments[0], _callHandlerID, JSON.stringify(Array.prototype.slice.call(arguments, 1)));return new Promise(function (resolve, reject) {window.flutter_inappwebview[_callHandlerID] = resolve;});};}preprocessorJS();
}preprocessorReadyJs()''';
三、在flutter端使用InAppWebViewController实现调用方法的InAppWebJSHandlerManager
在flutter端使用InAppWebViewController实现调用方法,使用InAppWebViewController来调用evaluateJavascript来调用js的方法。我们可以在flutter端实现调用flutter_inappwebview插件的JavaScript Handlers。这里我定义了消息队列,定义了消息的JSMessage,与responseCallbacks回调。
// flutter_inappwebview插件的JavaScript Handlers
class InAppWebJSHandlerManager {
// flutter_inappwebview插件InAppWebViewController? inAppWebViewController;BuildContext? context;// 存储的消息messageHandlerMap<String, dynamic> messageHandlers = {};// 存储的回调callback, responseCallbackMap<String, dynamic> responseCallbacks = {};// 开启的消息队列,发送的消息均会存储到该队列中List<JSMessage>? startupMessageQueue = [];// 消息的标识int _uniqueId = 0;JSChannelManager() {// TODO: implement JSChannelManager}void updateController({required BuildContext context,InAppWebViewController? inAppWebViewController,}) {this.context = context;this.inAppWebViewController = inAppWebViewController;}// 处理消息队列void flutterFlushMessageQueue() async {// 获取h5发送的列表// 处理H5存的消息队列发送的MessageQueueString? messageQueueString;if (inAppWebViewController != null) {messageQueueString = await inAppWebViewController?.evaluateJavascript(source: webViewJavascriptFetchQueyCommand());}LoggerManager().debug("flutterFlushMessageQueue:${messageQueueString}");flushMessageQueue(messageQueueString);}// 处理来自H5的消息列表void flushMessageQueue(String? messageQueueString) {if (!(messageQueueString != null && messageQueueString.isNotEmpty)) {return;}dynamic? aFromH5Messages = jsonDecode(messageQueueString);if (aFromH5Messages != null && aFromH5Messages is List) {for (dynamic aMsgJson in aFromH5Messages) {if (aMsgJson is Map<String, dynamic>) {JSMessage jsMessage = JSMessage.fromJson(aMsgJson);LoggerManager().debug("flushMessageQueue aFromH5Messages aMsgJson:${aMsgJson} jsMessage:${jsMessage}");// 从H5获取或者接收到的消息,如果responseId不为空,则为flutter调用H5方法,H5给flutter的回调if (jsMessage.responseId != null &&jsMessage.responseId!.isNotEmpty) {// 如果responseId不为空,则为flutter调用H5方法,H5给flutter的回调ResponseCallback? responseCallback =responseCallbacks[jsMessage.responseId];if (responseCallback != null) {// 处理H5返回给flutter的回调responseCallback(jsMessage.responseData);}} else {ResponseCallback? responseCallback;// 如果responseId为空时候,则是来自H5发送的flutter的消息// 获取H5传过来的标识callbackIdString? callbackId = jsMessage.callbackId;if (callbackId != null && callbackId.isNotEmpty) {// 接收到来自H5的消息JSMessage aMessage = JSMessage();aMessage.copy(aNewMessage: aMessage, aOldMessage: jsMessage);responseCallback = (dynamic responseData) {// flutter回调给H5// 将H5传过来的callbackId作为responseId回调传递给H5aMessage.responseId = callbackId;aMessage.responseData = responseData;_queueMessage(aMessage);};} else {responseCallback = (dynamic responseData) {// callbackId为空,不做任何处理};}// 从flutter已经注册Register方法中找出对应的方法JSBridgeHandler? jsBridgeHandler =messageHandlers[jsMessage.handlerName];if (jsBridgeHandler != null) {// 在flutter该handlerName的方法已经注册registerjsBridgeHandler(jsMessage.data, responseCallback);} else {// 在flutter该handlerName没有注册,则不做任何处理}}}}}}// 处理从H5收到的消息void _dispatchMessage(JSMessage message) async {String messageJSON = jsonEncode(message.toJson());messageJSON = messageJSON.replaceAll("\\", "\\\\");messageJSON = messageJSON.replaceAll("\"", "\\\"");messageJSON = messageJSON.replaceAll("\'", "\\\'");messageJSON = messageJSON.replaceAll("\n", "\\n");messageJSON = messageJSON.replaceAll("\r", "\\r");messageJSON = messageJSON.replaceAll("\f", "\\f");messageJSON = messageJSON.replaceAll("\u2028", "\\u2028");messageJSON = messageJSON.replaceAll("\u2029", "\\u2029");String javascriptCommand =webViewJavascriptHandleMessageFromObjCCommand(messageJSON);if (inAppWebViewController != null) {await inAppWebViewController?.evaluateJavascript(source: javascriptCommand);}}// 注入jsvoid injectJavascript(String javascript) async {if (inAppWebViewController != null) {await inAppWebViewController?.evaluateJavascript(source: javascript);}}// 注入jsvoid injectJavascriptReady() async {if (inAppWebViewController != null) {await inAppWebViewController?.evaluateJavascript(source: '$kWebviewJsBridgeReady');}}// 注入jsvoid injectBridgeJavascript() async {if (inAppWebViewController != null) {await inAppWebViewController?.evaluateJavascript(source: '$kInAppWebViewJavascriptBridge');}LoggerManager().debug("injectJavascript");// 处理flutter发送的消息队列if (startupMessageQueue != null && startupMessageQueue!.isNotEmpty) {List<JSMessage> tmpList = startupMessageQueue!;startupMessageQueue = null;for (JSMessage message in tmpList) {_dispatchMessage(message);}}}// 向H5发送消息void _sendData(String handleName,{dynamic? data, ResponseCallback? responseCallback}) {String callbackId = "flutter_cb_${++_uniqueId}";JSMessage jsMessage = JSMessage();jsMessage.callbackId = callbackId;jsMessage.handlerName = handleName;jsMessage.data = data;// 将callbackId存储到responseCallbacks中,callbackId会被H5通过responseId返回if (responseCallback != null) {responseCallbacks[callbackId] = responseCallback;}_queueMessage(jsMessage);}// 将发送给H5的消息存到startupMessageQueue中void _queueMessage(JSMessage jsMessage) {if (startupMessageQueue != null) {startupMessageQueue!.add(jsMessage);}_dispatchMessage(jsMessage);}// 注入js的commandString webViewJavascriptCheckCommand() {return "typeof window.AppJSBridge == \'object\';";}String webViewJavascriptFetchQueyCommand() {return "AppJSBridge._fetchQueue();";}String webViewJavascriptHandleMessageFromObjCCommand(String messageJSON) {return "AppJSBridge._handleMessageFromObjC('${messageJSON}');";}// 判断AppJSBridgeFuture<String?> checkJavascriptBridge() async {String? result;if (inAppWebViewController != null) {bool jsBridge = await inAppWebViewController?.evaluateJavascript(source: webViewJavascriptCheckCommand());result = (jsBridge?"true":"false");}LoggerManager().debug("checkJavascriptBridge result:${result}");return result;}/// flutter开放出去的方法,flutter调用H5方法统一使用该callHandler/// callHandlervoid callHandler(String handleName,{dynamic? data, ResponseCallback? responseCallback}) {if (handleName.isNotEmpty) {_sendData(handleName, data: data, responseCallback: responseCallback);}}/// flutter注册方法/// flutter注册方法,提供给H5调用void registerHandler(String handlerName, JSBridgeHandler jsBridgeHandler) {if (handlerName.isNotEmpty) {messageHandlers[handlerName] = jsBridgeHandler;}}void addJSBridgeHandlers() {if (inAppWebViewController != null) {messageHandlers.forEach((handlerName, jsBridgeHandler) {inAppWebViewController?.addJavaScriptHandler(handlerName: handlerName, callback: (List<dynamic> arguments) {LoggerManager().debug("inAppWebViewController.addJavaScriptHandler arguments:${arguments}");flutterFlushMessageQueue();});});}}// 移除注册的方法void removeHandler(String handleName) {if (handleName.isNotEmpty) {messageHandlers.remove(handleName);}}// 重置,将responseCallbacks、startupMessageQueue重置void reset() {startupMessageQueue = [];responseCallbacks = {};_uniqueId = 0;}
}
InAppWebJSHandlerManager中使用inAppWebViewController?.addJavaScriptHandler来处理接收H5端的JS消息,并且将处理回调返回给H5。
当收到H5消息,flutter根据callbackId将处理后的结果回调给H5。
四、InAppWebJSBridgeRegister:appBridge调用的方法,flutter注册的方法
InAppWebJSBridgeRegister实现处理flutter注册的方法,提供相应的方法,H5端JS可以方法调用。
// appBridge调用的方法,flutter注册的方法
class InAppWebJSBridgeRegister {late InAppWebJSHandlerManager _inAppWebJSHandlerManager;// 支付final ChannelPayPlatform _channelPayPlatform = ChannelPayPlatform();// 打开app等final ChannelLauncher _channelLauncher = ChannelLauncher();// 弹窗final ChannelDialog _channelDialog = ChannelDialog();// 扫码或者识别二维码final ChannelQrScanner _channelQrScanner = ChannelQrScanner();InAppWebJSBridgeRegister({required InAppWebJSHandlerManager inAppWebJSHandlerManager}) {_inAppWebJSHandlerManager = inAppWebJSHandlerManager;}// 注册handlersvoid registerHandlers({JSChannelRegisterHandler? jsChannelRegisterHandler}) {// 设置标题_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.setTitle,(data, responseCallback) {if (data != null && data is String) {String title = data;if (jsChannelRegisterHandler != null) {jsChannelRegisterHandler(JSChannelRegisterMethod.setTitle, title);}}});// 获取用户昵称_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getUsername,(data, responseCallback) {UserModel userModel =Provider.of<UserModel>(OneContext().context!, listen: false);String userNickName = userModel.userNickName ?? "";if (responseCallback != null) {responseCallback(userNickName);}});// 获取定位_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getLoc,(data, responseCallback) {// TODO 获取定位});// 获取App名称_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getAppName,(data, responseCallback) {PackageInfo.fromPlatform().then((packageInfo) {String appName = "${packageInfo.appName}";if (responseCallback != null) {responseCallback(appName);}});});// 获取版本号_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getVersion,(data, responseCallback) {PackageInfo.fromPlatform().then((packageInfo) {String version = "${packageInfo.buildNumber}";String versionCode = version.replaceAll(".", "");if (responseCallback != null) {responseCallback(versionCode);}});});// 获取用户id_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getUserId,(data, responseCallback) {UserModel userModel =Provider.of<UserModel>(OneContext().context!, listen: false);String userId = userModel.userId ?? "";if (responseCallback != null) {responseCallback(userId);}});// 获取用户登录认证token_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.getAuthorization,(data, responseCallback) {UserModel userModel =Provider.of<UserModel>(OneContext().context!, listen: false);String token = userModel.token ?? "";if (responseCallback != null) {responseCallback(token);}});// 调用支付(微信支付/支付宝支付)原生_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.setPayPlatform,(data, responseCallback) {_channelPayPlatform.openUniPay(data, responseCallback);});// 打开扫一扫_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.openScan,(data, responseCallback) {// 打开扫一扫界面_channelQrScanner.openScanner(JSChannelRegisterMethod.openScan, data, responseCallback);});// 打开扫一扫_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.scanQrCode,(data, responseCallback) {// 打开扫一扫界面_channelQrScanner.openScanner(JSChannelRegisterMethod.scanQrCode, data, responseCallback);});// 打系统电话_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.callTelPhone,(data, responseCallback) {_channelLauncher.openLauncher(JSChannelRegisterMethod.callTelPhone, data, responseCallback);});// 发送短信_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.sendSms,(data, responseCallback) {_channelLauncher.openLauncher(JSChannelRegisterMethod.sendSms, data, responseCallback);});// 对话框 showDialog_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.showDialog,(data, responseCallback) {_channelDialog.openShowDialog(data, responseCallback);});// 底部选择框_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.showCheckBox,(data, responseCallback) {_channelDialog.openShowSheetBox(data, responseCallback);});// 保存图片到相册_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.saveImage,(data, responseCallback) {// 保存图片到相册if (data != null && data is String && data.isNotEmpty) {FlutterLoadingHud.showLoading(message: "保存中...");SaveToAlbumUtil.saveImage(data, onCallback: (bool result, String message) {FlutterLoadingHud.dismiss();if (result) {// 保存成功FlutterLoadingHud.showToast(message: message);} else {// 保存失败FlutterLoadingHud.showToast(message: message);}});}});// 识别二维码_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.detectorQRCode,(data, responseCallback) {// 识别图片中的二维码_channelQrScanner.openScanner(JSChannelRegisterMethod.detectorQRCode, data, responseCallback);});// 打开App_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.openApp,(data, responseCallback) {_channelLauncher.openLauncher(JSChannelRegisterMethod.openApp, data, responseCallback);});// log_inAppWebJSHandlerManager.registerHandler(JSChannelRegisterMethod.log,(data, responseCallback) {Map<String, dynamic> dataJson = jsonDecode(data);int loggerType = dataJson["logType"];String message = dataJson["message"];if (LoggerMode.debug == loggerType) {LoggerManager().debug("registerHandlers log data: ${message}");} else if (LoggerMode.verbose == loggerType) {LoggerManager().verbose("registerHandlers log data: ${message}");} else if (LoggerMode.info == loggerType) {LoggerManager().info("registerHandlers log data: ${message}");} else if (LoggerMode.warning == loggerType) {LoggerManager().warning("registerHandlers log data: ${message}");} else if (LoggerMode.error == loggerType) {LoggerManager().error("registerHandlers log data: ${message}");}});_inAppWebJSHandlerManager.addJSBridgeHandlers();}// 处理是否跳转,true可跳转,false不可跳转bool shouldOverrideUrlLoading(Uri uri) {///在页面跳转之前调用 isForMainFrame为false,页面不跳转.导致网页内很多链接点击没效果String url = uri.toString();return webViewNavigationAllowed(url);}// 处理是否跳转,true可跳转,false不可跳转bool webViewNavigationAllowed(String url) {LoggerManager().debug('navigationDelegate decode $url');String telPrefix = "tel://";String smsPrefix = "sms://";String appPrefix = "app://";if (url.startsWith(telPrefix)) {String data = url.substring(telPrefix.length);_channelLauncher.openLauncher(JSChannelRegisterMethod.callTelPhone, data, null);// 不可跳转return false;}if (url.startsWith(smsPrefix)) {String data = url.substring(smsPrefix.length);_channelLauncher.openLauncher(JSChannelRegisterMethod.sendSms, data, null);// 不可跳转return false;}if (url.startsWith(appPrefix)) {// app://close_channelLauncher.openappUrl(url);return false;}if (url == "about:blank") {// 空页面进行跳转return true;}// 可跳转return true;}
}
五、定义JSMessage:H5与flutter交互的消息体
class JSMessage {// {handlerName: getSessionID, data: , callbackId: cb_2_1665631238605}// handlerNameString? handlerName;// data// flutter发送给H5的data,参数dynamic? data;/// callbackId,/// H5发送给flutter的callbackId,/// flutter处理后将调用 AppJSBridge._handleMessageFromObjC('%@');/// H5从responseCallbacks中根据callbackId找到callback回调方法进行执行String? callbackId;/// responseId/// flutter发送给H5的responseId,/// responseId和callbackId是一样的/// 如果是H5调用flutter时候,从H5过来的callbackId作为responseId回调给H5/// 如果是flutter调用H5,从flutter过来的callbackId作为responseId回调给flutterString? responseId;/// 回调的数据/// 如果是H5调用flutter时候,从flutter传给H5的responseData作为回调数据/// 如果是flutter调用H5,从H5传给flutter的responseData作为回调数据dynamic? responseData;JSMessage();JSMessage.fromJson(Map<String, dynamic> json) {callbackId = json['callbackId'];data = json['data'];handlerName = json['handlerName'];responseId = json['responseId'];responseData = json['responseData'];}Map<String, dynamic> toJson() {final Map<String, dynamic> data = new Map<String, dynamic>();data['callbackId'] = this.callbackId;data["data"] = this.data;data["handlerName"] = this.handlerName;data['responseId'] = this.responseId;data['responseData'] = this.responseData;return data;}void copy({required JSMessage aNewMessage, required JSMessage aOldMessage}) {aNewMessage.callbackId = aOldMessage.callbackId;aNewMessage.data = aOldMessage.data;aNewMessage.handlerName = aOldMessage.handlerName;aNewMessage.responseId = aOldMessage.responseId;aNewMessage.responseData = aOldMessage.responseData;}
}
六、封装inwebview中使用InAppWebJSBridgeRegister、InAppWebJSHandlerManager
我们封装inwebview中,使用InAppWebJSBridgeRegister、InAppWebJSHandlerManager。
定义InAppWebJSHandlerManager
// JS与Flutter调用的message Queuefinal InAppWebJSHandlerManager _inAppWebJSHandlerManager =InAppWebJSHandlerManager();
定义InAppWebJSBridgeRegister
// flutter注册供H5调用的方法late InAppWebJSBridgeRegister _inAppWebJSBridgeRegister;_inAppWebJSBridgeRegister = InAppWebJSBridgeRegister(inAppWebJSHandlerManager: _inAppWebJSHandlerManager);
我们在onWebViewCreated中,可以得到InAppWebViewController,更新_inAppWebJSHandlerManager的controller
_inAppWebJSHandlerManager.updateController(context: context,inAppWebViewController: webViewController,);
在onWebViewCreated回调中注入ready代码
// 注入jsReady_inAppWebJSHandlerManager.injectJavascriptReady();
在onWebViewCreated回调中注册flutter端的方法,
// register a JavaScript handler with name "myHandlerName"_inAppWebJSBridgeRegister.registerHandlers(jsChannelRegisterHandler: (handlerName, data) {if (JSChannelRegisterMethod.setTitle == handlerName) {setWebPageTitle(data);}});
在onLoadStop回调中注入kInAppWebViewJavascriptBridge
onLoadStop: (controller, url) async {// 注入_inAppWebJSHandlerManager.injectBridgeJavascript();_inAppWebJSHandlerManager.checkJavascriptBridge();// 加载完成widget.onLoadFinished(url.toString());},
完整代码如下
class WebViewInAppScreen extends StatefulWidget {const WebViewInAppScreen({Key? key,required this.url,this.onWebProgress,this.onWebResourceError,required this.onLoadFinished,required this.onWebTitleLoaded,this.onWebViewCreated,}) : super(key: key);final String url;final Function(int progress)? onWebProgress;final Function(String? errorMessage)? onWebResourceError;final Function(String? url) onLoadFinished;final Function(String? webTitle)? onWebTitleLoaded;final Function(InAppWebViewController controller)? onWebViewCreated;@overrideState<WebViewInAppScreen> createState() => _WebViewInAppScreenState();
}class _WebViewInAppScreenState extends State<WebViewInAppScreen> {final GlobalKey webViewKey = GlobalKey();InAppWebViewController? webViewController;InAppWebViewGroupOptions options = InAppWebViewGroupOptions(crossPlatform: InAppWebViewOptions(useShouldOverrideUrlLoading: true,mediaPlaybackRequiresUserGesture: false,applicationNameForUserAgent: "app-webview",),android: AndroidInAppWebViewOptions(useHybridComposition: true,),ios: IOSInAppWebViewOptions(allowsInlineMediaPlayback: true,),);// JS与Flutter调用的message Queuefinal InAppWebJSHandlerManager _inAppWebJSHandlerManager =InAppWebJSHandlerManager();// cookiefinal InAppWebJSCookieConfig _inAppWebViewJSCookieConfig =InAppWebJSCookieConfig();// flutter注册供H5调用的方法late InAppWebJSBridgeRegister _inAppWebJSBridgeRegister;bool _isDisposed = false;@overridevoid initState() {// TODO: implement initStatesuper.initState();_isDisposed = false;_inAppWebJSBridgeRegister = InAppWebJSBridgeRegister(inAppWebJSHandlerManager: _inAppWebJSHandlerManager);}@overridevoid dispose() {// TODO: implement dispose_isDisposed = true;_inAppWebJSHandlerManager.reset();webViewController?.clearCache();// _inAppWebViewJSCookieConfig.clear();super.dispose();}// 设置页面标题void setWebPageTitle(data) {if (widget.onWebTitleLoaded != null) {widget.onWebTitleLoaded!(data);}}// flutter调用H5方法void callJSMethod() {_inAppWebJSHandlerManager.callHandler("JSAPPHandler",data: {"id": "a18c9fe0d"}, responseCallback: (dynamic responseData) {LoggerManager().debug("callJSMethod responseData:${responseData}");FlutterLoadingHud.showToast(message: jsonEncode(responseData));});}@overrideWidget build(BuildContext context) {return Column(children: <Widget>[Expanded(child: InAppWebView(key: webViewKey,initialUrlRequest: URLRequest(url: Uri.parse(widget.url)),initialUserScripts: UnmodifiableListView<UserScript>([UserScript(source:"document.cookie='token=${ApiAuth.getToken()};domain='.inice.cn';path=/'",injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START),]),initialOptions: options,onWebViewCreated: (controller) {webViewController = controller;_inAppWebViewJSCookieConfig.setCookie(widget.url);_inAppWebJSHandlerManager.updateController(context: context,inAppWebViewController: webViewController,);// 注入jsReady_inAppWebJSHandlerManager.injectJavascriptReady();// register a JavaScript handler with name "myHandlerName"_inAppWebJSBridgeRegister.registerHandlers(jsChannelRegisterHandler: (handlerName, data) {if (JSChannelRegisterMethod.setTitle == handlerName) {setWebPageTitle(data);}});String filePre = "file://";if (widget.url.startsWith(filePre)) {String html = widget.url.substring(filePre.length);webViewController?.loadFile(assetFilePath: 'assets/htmls/${html}');} else {if (widget.url.startsWith("http://") ||widget.url.startsWith("https://")) {webViewController?.loadUrl(urlRequest: URLRequest(url: Uri.parse(widget.url)));}}if (widget.onWebViewCreated != null) {widget.onWebViewCreated!(controller);}},onTitleChanged: (controller, title) {if (widget.onWebTitleLoaded != null) {widget.onWebTitleLoaded!(title);}},onLoadStart: (controller, url) {},androidOnPermissionRequest: (controller, origin, resources) async {return PermissionRequestResponse(resources: resources,action: PermissionRequestResponseAction.GRANT);},shouldOverrideUrlLoading: (controller, navigationAction) async {var uri = navigationAction.request.url!;bool canNavigate =_inAppWebJSBridgeRegister.shouldOverrideUrlLoading(uri);// 允许路由替换return canNavigate? NavigationActionPolicy.ALLOW: NavigationActionPolicy.CANCEL;},onLoadStop: (controller, url) async {// 注入_inAppWebJSHandlerManager.injectBridgeJavascript();_inAppWebJSHandlerManager.checkJavascriptBridge();// 加载完成widget.onLoadFinished(url.toString());},onProgressChanged: (controller, progress) {if (widget.onWebProgress != null) {widget.onWebProgress!(progress);}},onLoadError: (controller, Uri? url, int code, String message) {if (widget.onWebResourceError != null) {widget.onWebResourceError!(message);}},onUpdateVisitedHistory: (controller, url, androidIsReload) {print("onUpdateVisitedHistory:${url}");},onConsoleMessage: (controller, consoleMessage) {print("consoleMessage:${consoleMessage}");},),),Container(height: ScreenUtil().bottomBarHeight + 50.0,color: Colors.white,child: Column(children: [Expanded(child: Row(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[ElevatedButton(child: Icon(Icons.arrow_back),onPressed: () {webViewController?.goBack();},),SizedBox(width: 25.0,),ElevatedButton(child: Icon(Icons.arrow_forward),onPressed: () {webViewController?.goForward();},),SizedBox(width: 25.0,),ElevatedButton(child: Icon(Icons.refresh),onPressed: () {// callJSMethod();webViewController?.reload();},),],),),Container(height: ScreenUtil().bottomBarHeight,),],),),],);}
}
七、使用inappwebview的page
最后,我们使用inappwebview,使用一个页面打开对应的需要的链接地址,这里使用的本地测试页面
class InAppWebViewPage extends StatefulWidget {const InAppWebViewPage({Key? key, this.arguments}) : super(key: key);final Object? arguments;@overrideState<InAppWebViewPage> createState() => _InAppWebViewPageState();
}class _InAppWebViewPageState extends State<InAppWebViewPage> {String title = "";String? url;double webProgress = 0.0;@overridevoid initState() {// TODO: implement initStateif (widget.arguments != null && widget.arguments is Map) {Map obj = widget.arguments as Map;url = obj["url"];}LoggerManager().debug("_WebViewPageState arguments:${widget.arguments}");LoggerManager().debug("_WebViewPageState url:${url}");super.initState();}@overridevoid dispose() {// TODO: implement disposesuper.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(resizeToAvoidBottomInset: false,appBar: WebAppBar(toolbarHeight: 44.0,backgroundColor: Theme.of(context).primaryColor,centerWidget: Text("${title}",textAlign: TextAlign.center,overflow: TextOverflow.ellipsis,style: TextStyle(fontSize: 17,color: ColorUtil.hexColor(0xffffff),fontWeight: FontWeight.w600,fontStyle: FontStyle.normal,decoration: TextDecoration.none,),),leadingWidget: Row(mainAxisAlignment: MainAxisAlignment.start,crossAxisAlignment: CrossAxisAlignment.center,children: [IconButton(padding: EdgeInsets.all(0.0),onPressed: () {navigatorBack();},icon: Icon(Icons.close_rounded,color: Colors.white,size: 30.0,),),],),trailingWidget: Row(mainAxisAlignment: MainAxisAlignment.end,crossAxisAlignment: CrossAxisAlignment.center,children: [SizedBox(width: 28.0,),SizedBox(width: 28.0,),],),),body: Stack(children: [WebViewInAppScreen(url: url ?? "",onWebProgress: (int progress) {if (mounted) {// TODO onWebProgressdouble precent = progress / 100.0;if (precent > 1.0) {precent = 1.0;}if (precent < 0.0) {precent = 0.0;}setState(() {webProgress = precent;LoggerManager().debug("webProgress:${webProgress}");});}},onLoadFinished: (String? url) {if (mounted) {// TODO onLoadFinished}},onWebTitleLoaded: (String? webTitle) {if (mounted) {setState(() {title = webTitle ?? "";});}},),buildProgressIndicator(context),],),);}Widget buildProgressIndicator(BuildContext context) {return (webProgress != 1.0)? LinearProgressIndicator(backgroundColor: Colors.transparent,valueColor: AlwaysStoppedAnimation(ColorUtil.hexColor(0x3b93ff)),value: webProgress,minHeight: 2,): Container();}void navigatorBack() {NavigatorPageRouter.pop();}
}
到此,inappwebview实现flutter与Javascript的交互JSBridge基本内容已经完成了。
查看效果
八、小结
inappwebview实现flutter与Javascript的交互JSBridge。描述可能不是特别准确,请见谅。
https://blog.csdn.net/gloryFlow/article/details/133667017
学习记录,每天不停进步。