flutter开发实战-实现webview与Javascript通信JSBridge

flutter开发实战-实现webview与H5中Javascript通信JSBridge

在开发中,使用到webview,flutter实现webview是使用原生的插件实现,常用的有webview_flutter与flutter_inappwebview
这里使用的是webview_flutter,在iOS上,WebView小部件由WKWebView支持。在Android上,WebView小部件由WebView支持。

在这里插入图片描述

这里使用的是webview_flutter的3.0.4版本,不同版本代码变化还是挺大的。

一、引webview_flutter

在工程中pubspec.yaml引入webview_flutter

  # 浏览器webview_flutter: ^3.0.4webview_cookie_manager: ^2.0.6

二、使用webview

2.1、webview

webview的属性

const WebView({Key? key,this.onWebViewCreated,this.initialUrl,this.initialCookies = const <WebViewCookie>[],this.javascriptMode = JavascriptMode.disabled,this.javascriptChannels,this.navigationDelegate,this.gestureRecognizers,this.onPageStarted,this.onPageFinished,this.onProgress,this.onWebResourceError,this.debuggingEnabled = false,this.gestureNavigationEnabled = false,this.userAgent,this.zoomEnabled = true,this.initialMediaPlaybackPolicy =AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,this.allowsInlineMediaPlayback = false,this.backgroundColor,})

flutter webview和JS交互,需要JavaScript开启。
flutter webview中的javascriptMode参数启用或禁用 JavaScript。默认情况下WebView的 JavaScript是禁用的,所以要想启用的话,可以使用JavascriptMode.unrestricted

WebView(initialUrl: 'https://www.laileshuo.com',javascriptMode: JavascriptMode.unrestricted,
)

flutter webview提供WebViewController来获取webview信息以及控制webview的刷新、loadUrl、前进、后退等功能。

WebView(initialUrl: 'https://www.laileshuo.com',onWebViewCreated: (WebViewController webViewController) {_controller = webViewController;},
);

2.2、JavascriptChannel

JavascriptChannel用于接收在web视图中运行的JavaScript代码发出的消息,提供了name与onMessageReceived。

JavascriptChannel({required this.name,required this.onMessageReceived,})

我们需要在Webview的javascriptChannels属性设置javascriptChannel!

javascriptChannels: <JavascriptChannel>{_jsChannelManager.javascriptChannel!,},

2.3、Cookie

在使用webview的cookie时候,使用initialCookies设置cookie列表
这里我们定义了JSCookieConfig来设置需要设置的cookie

// 处理注入到webview的cookie,设置cookie通过webview_cookie_manager设置所需要的cookie列表
// Cookie:不同应用对应不同的key,value为token
class JSCookieConfig {JSCookieConfig() {eventListener();}// cookiefinal WebviewCookieManager cookieManager = WebviewCookieManager();List<WebViewCookie> initialCookies() {LoggerManager().debug("initialCookies ApiAuth().token:${ApiAuth.getToken()}");List<WebViewCookie> cookies = [WebViewCookie(name: "app_authorization",value: ApiAuth.getToken(),domain: ".ifour.cn"),WebViewCookie(name: "token", value: ApiAuth.getToken(), domain: ".ifour.cn"),];return cookies;}Future<void> setCookies() async {// final mainCookie = Cookie('app_authorization', 'ApiAuth().token')..domain = 'ifour.cn';// final h5_tokenCookie = Cookie('token', 'ApiAuth().token')..domain = 'ifour.cn';//// await cookieManager.setCookies([//   mainCookie,//   h5_tokenCookie// ]);await cookieManager.setCookies([Cookie("app_authorization", ApiAuth.getToken())..domain = '.ifour.cn'..httpOnly = false,Cookie("token", ApiAuth.getToken())..domain = '.ifour.cn'..httpOnly = false,]);}Future<void> clear() async {await cookieManager.clearCookies();}void eventListener() {AppEventBus().on(kUserLoginChanged, this, (arg) {setCookies();});}// 注入cookie
// String cookieJS =
//     "document.cookie ='app_authorization=${ApiAuth().token};domain=.ifour.cn;path=/'";
//
// _jsChannelManager.injectJavascript(cookieJS);
}

2.4、注入JS

JSBridge实现webview上原生与h5的通信,js可以调用native,native也可以调用js,实现通信。
其主要是通过拦截 URL 请求来达到 native 端和 webview 端相互通信的效果,常用的是WebviewJavascriptBridge
这里我们使用代码将WebviewJavascriptBridge的JS代码注入到flutter webview中。

flutter使用的WebviewJavascriptBridge的代码

const String kWebviewJavascriptBridge = '''
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 CUSTOM_PROTOCOL_SCHEME = 'https';var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';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);// messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;// 通过JavaScriptChannel注入的全局对象window.JSAppSDK.postMessage(JSON.stringify(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);}// messagingIframe = document.createElement('iframe');// messagingIframe.style.display = 'none';// messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;// document.documentElement.appendChild(messagingIframe);registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);// setTimeout(_callWVJBCallbacks, 0);// function _callWVJBCallbacks() {// 	var callbacks = window.WVJBCallbacks;// 	delete window.WVJBCallbacks;// 	for (var i=0; i<callbacks.length; i++) {// 		callbacks[i](AppJSBridge);// 	}// }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);
}preprocessorJS()
''';

setupWebViewJavascriptBridge与setupWebViewJavascriptBridge判断window.AppJSBridge是否存在,通过监听AppJSBridgeReady来实现window.AppJSBridge初始化,之后js中就可以使用window.AppJSBridge中的registerHandler、callHandler等方法了。

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);}// if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }// window.WVJBCallbacks = [callback];// var WVJBIframe = document.createElement('iframe');// WVJBIframe.style.display = 'none';// WVJBIframe.src = 'https://__bridge_loaded__';// document.documentElement.appendChild(WVJBIframe);// setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)}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)});}
''';

在webview的onWebViewCreated将kWebviewJsBridgeReady代码注入,进行监听window.AppJSBridge是否可用。
注入的代码webController的runJavascript方法

_jsChannelManager中的代码

  // 注入jsvoid injectJavascriptReady() async {await webController?.runJavascript('javascript:$kWebviewJsBridgeReady');}

webview的onWebViewCreated,webview创建后

  onWebViewCreated: (controller) {LoggerManager().debug("onWebViewCreated");// 注入jsReady_jsChannelManager.injectJavascriptReady();},

在webview的onPageFinished将kWebviewJavascriptBridge代码注入

onPageFinished: (String url) {// 网页加载完成LoggerManager().debug('onPageFinished url: $url');// 注入_jsChannelManager.injectBridgeJavascript();},

2.5、实现JSChannelManager管理处理H5与flutter webview通信

JSChannelManager中使用JavascriptChannel来接收h5端的JS消息。
当收到H5消息的时候,flutter根据callbackId回调给H5,
实现的具体代码如下

const String kJSChannelName = "JSAppSDK";const String kOldProtocolScheme = "wvjbscheme";
const String kNewProtocolScheme = "https";
const String kQueueHasMessage = "__wvjb_queue_message__";
const String kBridgeLoaded = "__bridge_loaded__";class JSChannelManager {WebViewController? webController;BuildContext? context;JavascriptChannel? javascriptChannel;// 存储的消息messageHandlerMap<String, dynamic> messageHandlers = {};// 存储的回调callback, responseCallbackMap<String, dynamic> responseCallbacks = {};// 开启的消息队列,发送的消息均会存储到该队列中List<JSMessage>? startupMessageQueue = [];// 消息的标识int _uniqueId = 0;JSChannelManager() {javascriptChannel = JavascriptChannel(name: kJSChannelName,onMessageReceived: (JavascriptMessage message) {// 将JSON字符串转成MapLoggerManager().debug("onMessageReceived message:${message.message}");flutterFlushMessageQueue();},);}void updateController(WebViewController controller, BuildContext context) {this.webController = controller;this.context = context;}JavascriptChannel getJSChannel() {return javascriptChannel!;}// 处理消息队列void flutterFlushMessageQueue() async {// 获取h5发送的列表// 处理H5存的消息队列发送的MessageQueueString? messageQueueString = await webController?.runJavascriptReturningResult(webViewJavascriptFetchQueyCommand());LoggerManager().debug("flutterFlushMessageQueue:${messageQueueString}");flushMessageQueue(messageQueueString);}// 处理来自H5的消息列表void flushMessageQueue(String? messageQueueString) {if (!(messageQueueString != null && messageQueueString.isNotEmpty)) {return;}LoggerManager().debug("flushMessageQueue messageQueueString:${messageQueueString}");dynamic? aFromH5Messages = jsonDecode(messageQueueString);LoggerManager().debug("flushMessageQueue 1111 aFromH5Messages:${aFromH5Messages}, type:${aFromH5Messages.runtimeType}");if (aFromH5Messages != null && aFromH5Messages is String) {aFromH5Messages = jsonDecode(aFromH5Messages);}LoggerManager().debug("flushMessageQueue 222 aFromH5Messages:${aFromH5Messages}, type:${aFromH5Messages.runtimeType}");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);await webController?.runJavascript(javascriptCommand);}// 注入jsvoid injectJavascript(String javascript) async {await webController?.runJavascript(javascript);}// 注入jsvoid injectJavascriptReady() async {await webController?.runJavascript('javascript:$kWebviewJsBridgeReady');}// 注入jsvoid injectBridgeJavascript() async {await webController?.runJavascript('javascript:$kWebviewJavascriptBridge');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);}// 判断是否可以注入urlbool isWebViewJavascriptBridgeURL(String url) {if (!isSchemeMatch(url)) {return false;}return isBridgeLoadedURL(url) || isQueueMessageURL(url);}bool isSchemeMatch(String url) {String lowerUrl = url.toLowerCase();LoggerManager().debug("isSchemeMatch lowerUrl:${lowerUrl}");return (lowerUrl.startsWith(kNewProtocolScheme) ||lowerUrl.startsWith(kOldProtocolScheme));}bool isQueueMessageURL(String url) {String lowerUrl = url.toLowerCase();LoggerManager().debug("isQueueMessageURL lowerUrl:${lowerUrl}");return (isSchemeMatch(url) && (lowerUrl.contains(kQueueHasMessage)));}bool isBridgeLoadedURL(String url) {String lowerUrl = url.toLowerCase();LoggerManager().debug("isBridgeLoadedURL lowerUrl:${lowerUrl}");return (isSchemeMatch(url) && (lowerUrl.contains(kBridgeLoaded)));}// 注入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 = await webController?.runJavascriptReturningResult(webViewJavascriptCheckCommand());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 handleName, JSBridgeHandler jsBridgeHandler) {if (handleName.isNotEmpty) {messageHandlers[handleName] = jsBridgeHandler;}}// 移除注册的方法void removeHandler(String handleName) {if (handleName.isNotEmpty) {messageHandlers.remove(handleName);}}// 重置,将responseCallbacks、startupMessageQueue重置void reset() {startupMessageQueue = [];responseCallbacks = {};_uniqueId = 0;}
}

2.6、JSChannelRegister:appBridge调用的方法,flutter注册的方法

JSChannelRegister实现处理flutter注册的方法,提供相应的方法,H5端的JS可以方便调用。

// appBridge调用的方法,flutter注册的方法
class JSChannelRegister {late JSChannelManager _jsChannelManager;// 支付final ChannelPayPlatform _channelPayPlatform = ChannelPayPlatform();// 打开app等final ChannelLauncher _channelLauncher = ChannelLauncher();// 弹窗final ChannelDialog _channelDialog = ChannelDialog();// 扫码或者识别二维码final ChannelQrScanner _channelQrScanner = ChannelQrScanner();JSChannelRegister({required JSChannelManager jsChannelManager}) {_jsChannelManager = jsChannelManager;}// 注册handlersvoid registerHandlers({JSChannelRegisterHandler? jsChannelRegisterHandler}) {// 设置标题_jsChannelManager.registerHandler(JSChannelRegisterMethod.setTitle,(data, responseCallback) {if (data != null && data is String) {String title = data;if (jsChannelRegisterHandler != null) {jsChannelRegisterHandler(JSChannelRegisterMethod.setTitle, title);}}});// 获取用户昵称_jsChannelManager.registerHandler(JSChannelRegisterMethod.getUsername,(data, responseCallback) {UserModel userModel =Provider.of<UserModel>(OneContext().context!, listen: false);String userNickName = userModel.userNickName ?? "";if (responseCallback != null) {responseCallback(userNickName);}});// 获取定位_jsChannelManager.registerHandler(JSChannelRegisterMethod.getLoc,(data, responseCallback) {// TODO 获取定位});// 获取App名称_jsChannelManager.registerHandler(JSChannelRegisterMethod.getAppName,(data, responseCallback) {PackageInfo.fromPlatform().then((packageInfo) {String appName = "${packageInfo.appName}";if (responseCallback != null) {responseCallback(appName);}});});// 获取版本号_jsChannelManager.registerHandler(JSChannelRegisterMethod.getVersion,(data, responseCallback) {PackageInfo.fromPlatform().then((packageInfo) {String version = "${packageInfo.buildNumber}";String versionCode = version.replaceAll(".", "");if (responseCallback != null) {responseCallback(versionCode);}});});// 获取用户id_jsChannelManager.registerHandler(JSChannelRegisterMethod.getUserId,(data, responseCallback) {UserModel userModel =Provider.of<UserModel>(OneContext().context!, listen: false);String userId = userModel.userId ?? "";if (responseCallback != null) {responseCallback(userId);}});// 获取用户登录认证token_jsChannelManager.registerHandler(JSChannelRegisterMethod.getAuthorization,(data, responseCallback) {UserModel userModel =Provider.of<UserModel>(OneContext().context!, listen: false);String token = userModel.token ?? "";if (responseCallback != null) {responseCallback(token);}});// 调用支付(微信支付/支付宝支付)原生_jsChannelManager.registerHandler(JSChannelRegisterMethod.setPayPlatform,(data, responseCallback) {_channelPayPlatform.openUniPay(data, responseCallback);});// 打开扫一扫_jsChannelManager.registerHandler(JSChannelRegisterMethod.openScan,(data, responseCallback) {// 打开扫一扫界面_channelQrScanner.openScanner(JSChannelRegisterMethod.openScan, data, responseCallback);});// 打开扫一扫_jsChannelManager.registerHandler(JSChannelRegisterMethod.scanQrCode,(data, responseCallback) {// 打开扫一扫界面_channelQrScanner.openScanner(JSChannelRegisterMethod.scanQrCode, data, responseCallback);});// 打系统电话_jsChannelManager.registerHandler(JSChannelRegisterMethod.callTelPhone,(data, responseCallback) {_channelLauncher.openLauncher(JSChannelRegisterMethod.callTelPhone, data, responseCallback);});// 发送短信_jsChannelManager.registerHandler(JSChannelRegisterMethod.sendSms,(data, responseCallback) {_channelLauncher.openLauncher(JSChannelRegisterMethod.sendSms, data, responseCallback);});// 对话框 showDialog_jsChannelManager.registerHandler(JSChannelRegisterMethod.showDialog,(data, responseCallback) {_channelDialog.openShowDialog(data, responseCallback);});// 底部选择框_jsChannelManager.registerHandler(JSChannelRegisterMethod.showCheckBox,(data, responseCallback) {_channelDialog.openShowSheetBox(data, responseCallback);});// 保存图片到相册_jsChannelManager.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);}});}});// 识别二维码_jsChannelManager.registerHandler(JSChannelRegisterMethod.detectorQRCode,(data, responseCallback) {// 识别图片中的二维码_channelQrScanner.openScanner(JSChannelRegisterMethod.detectorQRCode, data, responseCallback);});// 打开App_jsChannelManager.registerHandler(JSChannelRegisterMethod.openApp,(data, responseCallback) {_channelLauncher.openLauncher(JSChannelRegisterMethod.openApp, data, responseCallback);});// log_jsChannelManager.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}");}});}// 处理是否跳转,true可跳转,false不可跳转bool navigationDecision(NavigationRequest request) {///在页面跳转之前调用 isForMainFrame为false,页面不跳转.导致网页内很多链接点击没效果String url = Uri.decodeComponent(request.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;}
}

使用JSChannelRegister,处理相应的callback

void initState() {// TODO: implement initStatesuper.initState();_isDisposed = false;_jsChannelRegister = JSChannelRegister(jsChannelManager: _jsChannelManager);_jsChannelRegister.registerHandlers(jsChannelRegisterHandler: (handlerName, data) {if (JSChannelRegisterMethod.setTitle == handlerName) {setWebPageTitle(data);}});}

2.7、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;}
}

三、H5前端

我这里使用的是本地Html文件,在JS中调用window.AppJSBridge中的方法,如callHandler、registerHandler。
Html示例代码

<!DOCTYPE html>
<html><head> <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><style type="text/css">body{background: #f5faff;}.button{width: 100%;line-height: 38px;text-align: center;font-weight: bold;color: #fff;text-shadow:1px 1px 1px #333;margin:0 auto;}.button:nth-child(6n){margin-right: 0;}.button.gray{color: #8c96a0;text-shadow:1px 1px 1px #fff;border:1px solid #dce1e6;box-shadow: 0 1px 2px #fff inset,0 -1px 0 #a8abae inset;background: -webkit-linear-gradient(top,#f2f3f7,#e4e8ec);background: -moz-linear-gradient(top,#f2f3f7,#e4e8ec);background: linear-gradient(top,#f2f3f7,#e4e8ec);}</style><title>JSBridge调用示例,常用方法调用</title></head> <body><button type="button" class="button gray" id="getUsername">getUsername</button><button type="button" class="button gray" id="getLoc">getLoc</button><button type="button" class="button gray" id="getVersion">getVersion</button><button type="button" class="button gray" id="scanQrCode">scanQrCode</button><button type="button" class="button gray" id="setMenuItems">setMenuItems</button><button type="button" class="button gray" id="callTelPhone">callTelPhone</button><button type="button" class="button gray" id="webImagePreview">webImagePreview</button><button type="button" class="button gray" id="showCheckBox">showCheckBox</button><button type="button" class="button gray" id="showDialog">showDialog</button><button type="button" class="button gray" id="saveImage">saveImage</button><button type="button" class="button gray" id="openApp">打开其他App</button><script>var imgURL = 'http://tupian.qqjay.com/tou3/2016/0726/fc4fe6f04843172bd6dbfeb5b6fe0686.jpg';var title = '分享券'var desc = '分享券描述内容'var url = 'http://www.laileshuo.com'var wxSharedObject = {thumb: imgURL,title: title,desc: desc,url: url};var appSharedObject = {thumb: imgURL,title: title,desc: desc,url: url};var getUsername=document.getElementById("getUsername");getUsername.addEventListener('click',function(){AppJSBridge.callHandler('getUsername', '',  function(response) {window.alert(response)});});var getLoc=document.getElementById("getLoc");getLoc.addEventListener('click',function(){AppJSBridge.callHandler('getLoc', '',  function(response) {window.alert(response)});});var getVersion=document.getElementById("getVersion");getVersion.addEventListener('click',function(){AppJSBridge.callHandler('getVersion', '',  function(response) {window.alert(response)});});var scanQrCode=document.getElementById("scanQrCode");scanQrCode.addEventListener('click',function(){AppJSBridge.callHandler('scanQrCode', '',  function(response) {window.alert(response)});});var setMenuItems=document.getElementById("setMenuItems");setMenuItems.addEventListener('click',function(){AppJSBridge.callHandler('setMenuItems', 'wxinFreind,wxinTime,weibo,refresh',  function(response) {});});var callTelPhone=document.getElementById("callTelPhone");var telPhone = '10086,10086';callTelPhone.addEventListener('click',function(){AppJSBridge.callHandler('callTelPhone', telPhone,  function(response) {// log('JS got response', response)});});var webImagePreview=document.getElementById("webImagePreview");var previewData = {'imgs' : [                                              //图片列表数组'http://7sbytg.com1.z0.glb.clouddn.com/yz2.png','http://7sbytg.com1.z0.glb.clouddn.com/yz2.png'],'index' : '0'                                           //进入预览时显示第几个图片};webImagePreview.addEventListener('click',function(){AppJSBridge.callHandler('webImagePreview', JSON.stringify(previewData),  function(response) {});});var showCheckBox=document.getElementById("showCheckBox");var bottomBox = {'optionList' : ['删除', '兑换', '其他']       //选项列表,选项列表对应自己的index};showCheckBox.addEventListener('click',function(){AppJSBridge.callHandler('showCheckBox', JSON.stringify(bottomBox),  function(response) {window.alert(response)});});var showDialog=document.getElementById("showDialog");var dialog = {'title' : '标题',             // Dialog标题'message' : '对话框内容',      // Dialog内容,可选'ok' : '确定',                // 确认按钮的文字,可选,不填时不显示该按钮'cancel' : '取消'             // 取消按钮的文字,可选,不填时不显示该按钮};showDialog.addEventListener('click',function(){AppJSBridge.callHandler('showDialog', JSON.stringify(dialog),  function(response) {// log('JS got response', response)});});var saveImage=document.getElementById("saveImage");saveImage.addEventListener('click',function(){AppJSBridge.callHandler('saveImage', 'https://c-ssl.duitang.com/uploads/item/201611/12/20161112230928_vJEQy.jpeg',  function(response) {});});var openApp=document.getElementById("openApp");openApp.addEventListener('click',function(){AppJSBridge.callHandler('openApp', 'weixin',  function(response) {});});if (window.AppJSBridge) {var dialog = {'title' : '标题',             // Dialog标题'message' : '对话框内容',      // Dialog内容,可选'ok' : '确定',                // 确认按钮的文字,可选,不填时不显示该按钮'cancel' : '取消'             // 取消按钮的文字,可选,不填时不显示该按钮};AppJSBridge.callHandler('showDialog', JSON.stringify(dialog),  function(response) {// log('JS got response', response)});}document.addEventListener('AppJSBridgeReady', function() {AppJSBridge.registerHandler('JSAPPHandler', function(data, responseCallback) {var responseData = { 'Javascript Says':'Right back atcha!' }responseCallback(responseData)});var dialog = {'title' : '标题',             // Dialog标题'message' : '对话框内容',      // Dialog内容,可选'ok' : '确定',                // 确认按钮的文字,可选,不填时不显示该按钮'cancel' : '取消'             // 取消按钮的文字,可选,不填时不显示该按钮};AppJSBridge.callHandler('showDialog', JSON.stringify(dialog),  function(response) {// log('JS got response', response)});}, false);//WKWebView 可用document.addEventListener('visibilitychange', () => {if (document.hidden) {// 页面被挂起window.alert(document.visibilityState)} else {// 页面呼出window.alert(document.visibilityState)}})</script></body>
</html>

四、flutter的webView_page页面打开对应的Html页面

这里使用的JSChannelManager、JSCookieConfig、JSChannelRegister等flutter

WebViewPage

class WebViewPage extends StatefulWidget {const WebViewPage({Key? key,this.arguments,}) : super(key: key);final Object? arguments;State<WebViewPage> createState() => _WebViewPageState();
}class _WebViewPageState extends State<WebViewPage> {String title = "";String? url;// WebViewControllerWebViewController? _webViewController;double webProgress = 0.0;void 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();}void dispose() {// TODO: implement disposesuper.dispose();}Widget build(BuildContext context) {return Scaffold(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: () {webViewGoBack();},icon: Icon(Icons.arrow_back_ios,color: Colors.white,size: 24.0,),),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,),IconButton(padding: EdgeInsets.all(0.0),onPressed: () {webViewReload();},icon: Icon(Icons.refresh_outlined,color: Colors.white,size: 28.0,),),],),),body: Stack(children: [WebViewSkeleton(url: url ?? "",onWebResourceError: (WebResourceError error) {if (mounted) {// TODO onWebResourceError}},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 ?? "";});}},onWebViewCreated: (WebViewController controller) {_webViewController = controller;},),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();}void webViewGoBack() {_webViewController?.canGoBack().then((res) {// 是否能返回上一级LoggerManager().debug("controller.canGoBack res: $res");if (true == res) {_webViewController?.goBack();} else {navigatorBack();}});}void webViewReload() {_webViewController?.reload();}
}

WebViewSkeleton

class WebViewSkeleton extends StatefulWidget {const WebViewSkeleton({Key? key,required this.url,required this.onWebProgress,required this.onWebResourceError,required this.onLoadFinished,this.onWebTitleLoaded,required this.onWebViewCreated,}) : super(key: key);final String url;final Function(int progress) onWebProgress;final Function(WebResourceError error) onWebResourceError;final Function(String? url) onLoadFinished;final Function(String? webTitle)? onWebTitleLoaded;final Function(WebViewController controller) onWebViewCreated;static GlobalKey<_WebViewSkeletonState> getGlobalKey() => GlobalKey();State<WebViewSkeleton> createState() => _WebViewSkeletonState();
}class _WebViewSkeletonState extends State<WebViewSkeleton> {// WebViewControllerWebViewController? _webController;// JS与Flutter调用的message Queuefinal JSChannelManager _jsChannelManager = JSChannelManager();// cookiefinal JSCookieConfig _jsCookieConfig = JSCookieConfig();// flutter注册供H5调用的方法late JSChannelRegister _jsChannelRegister;// 尝试3次,每次间隔2秒int _loadTitleTimes = 0;bool _isDisposed = false;void initState() {// TODO: implement initStatesuper.initState();_isDisposed = false;_jsChannelRegister = JSChannelRegister(jsChannelManager: _jsChannelManager);_jsChannelRegister.registerHandlers(jsChannelRegisterHandler: (handlerName, data) {if (JSChannelRegisterMethod.setTitle == handlerName) {setWebPageTitle(data);}});}void dispose() {// TODO: implement dispose_isDisposed = true;_jsChannelManager.reset();_webController?.clearCache();// _jsCookieConfig.clear();super.dispose();}// flutter调用H5方法void callJSMethod() {_jsChannelManager.callHandler("JSAPPHandler", data: {"id": "a18c9fe0d"},responseCallback: (dynamic responseData) {LoggerManager().debug("callJSMethod responseData:${responseData}");FlutterLoadingHud.showToast(message: jsonEncode(responseData));});}void webPageLoadedStart() {_loadTitleTimes = 0;}Future<void> getWebPageTitle({required String url}) async {if (_isDisposed) {return;}String? title = await _webController?.getTitle();LoggerManager().debug("getWebPageTitle:${title}");if (title != null && title.isNotEmpty) {LoggerManager().debug("webTitle a:${title}");setWebPageTitle(title);} else {try {String? result = await _webController?.runJavascriptReturningResult('window.document.title');LoggerManager().debug("webTitle document.url:${result}");if (result != null && result.isNotEmpty) {setWebPageTitle(result);} else {result = await _webController?.runJavascriptReturningResult('window.document.getElementsByTagName("title")[0]');LoggerManager().debug("webTitle document.getElementsByTagName:${result}");setWebPageTitle(result);}} catch (e) {print("getWebPageTitle:${e.toString()}");// 最多尝试三次if (_loadTitleTimes < 3) {Future.delayed(Duration(seconds: 2), () {_loadTitleTimes++;getWebPageTitle(url: url);});}}}}// 设置页面标题void setWebPageTitle(data) {if (widget.onWebTitleLoaded != null) {widget.onWebTitleLoaded!(data);}}// 返回void goBack() {_webController?.canGoBack().then((res) {// 是否能返回上一级LoggerManager().debug("controller.canGoBack res: $res");if (true == res) {_webController?.goBack();}});}// 刷新void reload() {_webController?.reload();}Widget build(BuildContext context) {return buildWebView(context);}Widget buildWebView(BuildContext context) {UserModel userModel = Provider.of<UserModel>(context, listen: false);LoggerManager().debug("ApiAuth().token:${ApiAuth.getToken()}");return WebView(debuggingEnabled: true,initialUrl: widget.url,javascriptMode: JavascriptMode.unrestricted,userAgent: "app-yjxdh-webview",initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,allowsInlineMediaPlayback: true,initialCookies: _jsCookieConfig.initialCookies(),onWebViewCreated: (controller) {LoggerManager().debug("onWebViewCreated");_jsCookieConfig.setCookies();// controller.loadUrl(url);此时也可以初始化一个urlcontroller.canGoBack().then((res) {// 是否能返回上一级LoggerManager().debug("controller.canGoBack res: $res");});controller.currentUrl().then((url) {// 返回当前urlLoggerManager().debug("controller.currentUrl url: $url");});controller.canGoForward().then((res) {//是否能前进LoggerManager().debug("controller.canGoForward res: $res");});_webController = controller;_jsChannelManager.updateController(controller, context);String filePre = "file://";if (widget.url.startsWith(filePre)) {String html = widget.url.substring(filePre.length);DefaultAssetBundle.of(context).loadString('assets/htmls/${html}').then((value) => _webController?.loadHtmlString(value));} else {if (widget.url.startsWith("http://") ||widget.url.startsWith("https://")) {_webController?.loadUrl(widget.url, headers: {'Referer': widget.url,});}}// 注入jsReady_jsChannelManager.injectJavascriptReady();widget.onWebViewCreated(controller);},onProgress: (int progress) {widget.onWebProgress(progress);},javascriptChannels: <JavascriptChannel>{_jsChannelManager.javascriptChannel!,},navigationDelegate: (NavigationRequest request) {bool canNavigate = _jsChannelRegister.navigationDecision(request);// 允许路由替换return canNavigate? NavigationDecision.navigate: NavigationDecision.prevent;},onPageStarted: (String url) {// 网页开始加载webPageLoadedStart();LoggerManager().debug('onPageStarted url: $url');},onPageFinished: (String url) {// 网页加载完成LoggerManager().debug('onPageFinished url: $url');// 注入_jsChannelManager.injectBridgeJavascript();_jsChannelManager.checkJavascriptBridge();// 加载完成widget.onLoadFinished(url);// 获取网页的标题getWebPageTitle(url: url);},gestureNavigationEnabled: true,backgroundColor: ColorUtil.hexColor(0xf7f7f7),onWebResourceError: (WebResourceError error) {/// errorLoggerManager().debug("onWebResourceError:${error}");widget.onWebResourceError(error);},);}Widget buildButtonRow(BuildContext context) {return Row(mainAxisAlignment: MainAxisAlignment.end,crossAxisAlignment: CrossAxisAlignment.center,children: [buildButton(context),SizedBox(width: 10.0,),buildRefreshButton(context),],);}// 展开的按钮Widget buildButton(BuildContext context) {return Container(decoration: BoxDecoration(color: Colors.white,border: Border.all(color: Colors.black26,width: 1.0,style: BorderStyle.solid,),borderRadius: BorderRadius.all(Radius.circular(8.0),),),child: TextButton(onPressed: () {callJSMethod();},child: Text('调用JS方法菜单',style: TextStyle(fontSize: 12,color: Colors.black,),),),);}// 刷新按钮Widget buildRefreshButton(BuildContext context) {return Container(decoration: BoxDecoration(color: Colors.white,border: Border.all(color: Colors.black26,width: 1.0,style: BorderStyle.solid,),borderRadius: BorderRadius.all(Radius.circular(8.0),),),child: TextButton(onPressed: () {reload();},child: Text('刷新WebView',style: TextStyle(fontSize: 12,color: Colors.black,),),),);}
}

六、运行效果图

在这里插入图片描述

在这里插入图片描述

五、小结

flutter开发实战-webview_flutter结合javascriptbridge实现flutter与html交互,通过使用flutter webview通过javascriptBridge来进行交互、交互用到了JavascriptChannel、cookie等。代码是好久之前写的,现在文档整理的有点乱,代码中基本上都有注释。希望有对你有用的点。

学习记录,每天不停进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/785.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量干货博客汇总https://blog.csdn.net/yu_cblog/c…

怎么把pytorch从CPU版本替换成GPU版本

使用pip命令pip uninstall torch就可以卸载当前的torch版本。&#xff08;不是cpu版本一般也没有必要重装吧&#xff1f;&#xff09; 接着找到官网https://pytorch.org/get-started/locally/ 在里面选择 根据你自己的需要选择最新的&#xff08;我现在是11.8&#xff09;或者没…

机器学习之随机森林(Random forest)

1 什么是随机森林 随机森林是一种监督式算法&#xff0c;使用由众多决策树组成的一种集成学习方法&#xff0c;输出是对问题最佳答案的共识。随机森林可用于分类或回归&#xff0c;是一种主流的集成学习算法。 1.1 随机森林算法原理 随机森林中有许多的分类树。我们要将一个输…

怎么使用Netty解码自定义通信协议

网络协议的基本要素 一个完备的网络协议需要具备哪些基本要素 魔数&#xff1a;魔数是通信双方协商的一个暗号&#xff0c;通常采用固定的几个字节表示。魔数的作用是防止任何人随便向服务器的端口上发送数据。协议版本号&#xff1a;随着业务需求的变化&#xff0c;协议可能…

OpenCV(图像处理)-图片搜索

图片搜索 1.知识介绍2.实现流程2.1 计算特征点与描述子2.2 描述子的匹配2.3 求出单应性矩阵并画出轮廓2.4 将特征点标出 此篇博客作者仍在探索阶段&#xff0c;还有一些模糊的概念没有弄懂&#xff0c;请读者自行分辨。 1.知识介绍 Opencv进行图片搜索需要的知识有&#xff1…

Leetcode-每日一题【147.对链表进行插入排序】

题目 给定单个链表的头 head &#xff0c;使用 插入排序 对链表进行排序&#xff0c;并返回 排序后链表的头 。 插入排序 算法的步骤: 插入排序是迭代的&#xff0c;每次只移动一个元素&#xff0c;直到所有元素可以形成一个有序的输出列表。 每次迭代中&#xff0c;插入排序…

青岛大学_王卓老师【数据结构与算法】Week05_09_顺序栈的操作3_学习笔记

本文是个人学习笔记&#xff0c;素材来自青岛大学王卓老师的教学视频。 一方面用于学习记录与分享&#xff0c; 另一方面是想让更多的人看到这么好的《数据结构与算法》的学习视频。 如有侵权&#xff0c;请留言作删文处理。 课程视频链接&#xff1a; 数据结构与算法基础…

ELK搭建

ELK介绍&#xff1a; ELK是一组开源工具的缩写&#xff0c;它由Elasticsearch、Logstash和Kibana三个组件组成&#xff0c;用于处理、分析和可视化大量日志数据。 入门级ELK搭建&#xff08;无Docker环境&#xff09; 安装前准备 1.获取安装包 https://artifacts.elastic…

Objective-C 父元素和子元素的点击事件

场景&#xff1a; &#xff08;需求1&#xff09;pageA一开始是【默认模式】&#xff0c;点击父元素view&#xff08;包括【搜索】文字&#xff09;&#xff0c;进入【搜索模式】&#xff1b; &#xff08;需求2&#xff09;在pageA中&#xff0c;点击【取消】文字时&#xff…

如何快速制作一个奶茶店小程序商城

如果你是一个奶茶店的老板&#xff0c;你可能会考虑开设一个小程序商城来增加销售渠道和提升品牌形象。那么&#xff0c;如何快速制作一个奶茶店小程序商城呢&#xff1f;下面我们将介绍一个简单的步骤供你参考。 首先&#xff0c;你需要登录乔拓云平台进入商城后台管理页面。在…

用ChatGPT解析Wireshark抓取的数据包样例

用Wireshark抓取的数据包&#xff0c;常用于网络故障排查、分析和应用程序通信协议开发。其抓取的分组数据结果为底层数据&#xff0c;看起来比较困难&#xff0c;现在通过chatGPT大模型&#xff0c;可以将原始抓包信息数据提交给AI进行解析&#xff0c;本文即是进行尝试的样例…

互联网行业真的不行了吗?

文章目录 前言一、起因二、互联网真的完了吗&#xff1f;三、是不是要转行&#xff1f;四、十年磨一剑五、统一回复 前言 英雄算法联盟 - 七月集训 已经开始 16 天&#xff0c;八月算法集训 将于 08月01日 正式开始&#xff0c;目前已经提前开始报名&#xff0c;报名方式参见&a…

OpenCv之滤波器

目录 一、卷积 二、方盒滤波与均值滤波 三、高斯滤波 四、中值滤波 五、双边滤波 一、卷积 图像卷积就是卷积核在图像上按行华东遍历像素时不断的相乘求和的过程 相关知识点: 步长:就是卷积核在图像上移动的步幅.(为充分扫描图片&#xff0c;步长一般为1)padding:指在图片…

matlab学习指南(1):matlab初步入门详细介绍

&#x1f305;*&#x1f539;** φ(゜▽゜*)♪ **&#x1f539;*&#x1f305; 欢迎来到馒头侠的博客&#xff0c;该类目主要讲数学建模的知识&#xff0c;大家一起学习&#xff0c;联系最后的横幅&#xff01; 喜欢的朋友可以关注下&#xff0c;私信下次更新不迷路&#xff0…

sqlite3交叉编译

1、交叉编译sqllite3可以先从官网下载最新最新的源码进行编译。sqlite3下载sqlite3有两种版本的源代码&#xff0c;sqlite-amalgamation-3420000.zip这种是将所有的操作放到sqlite3中进行使用的。虽然官方推荐使用这种方法。但是对于嵌入式移植还是使用sqlite-autoconf-3420000…

探索基于300W-LP的3D人脸关键点检测

目录 前言一、&#xff13;D 关键点可视化二、使用步骤1.300W-LP转为YOLO数据格式2.修改数据入口3.开始训练 总结 前言 300WLP数据集提供来丰富的人脸线索&#xff0c;包括&#xff12;D或&#xff13;D的关键点信息&#xff0c;Head Angle和&#xff13;DMM的参数等&#xff…

OpenCV中的RGB与YUV转换

1 基本概念 YUV 颜色空间从模拟电视时代开始就被广泛应用于彩色图像的转换与处理。其基于一个 3x3 的矩阵&#xff0c;通过线性变换将 RGB 像素转换为一个亮度&#xff08;Luma&#xff09;分量 Y 以及两个色度&#xff08;Chroma&#xff09;分量 U 和 V。由于模拟电视存在着多…

K8s 为什么要弃用 Docker

K8s 为什么要弃用 Docker 最近在学习容器技术的过程中&#xff0c;看到有关于Kubernetes“弃用 Docker”的事情&#xff0c;担心现在学 Docker 是否还有价值&#xff0c;是否现在就应该切换到 containerd 或者是其他 runtime。 随着深入了解&#xff0c;这些疑虑的确是有些道理…

git 工具使用--分支管理

git 工具使用–分支管理 文章目录 git 工具使用--分支管理理解分支创建分支切换分支合并分支删除分支合并冲突分支管理策略分支策略bug分支删除临时分支总结 理解分支 分支管理是Git的杀手级功能之一。分支&#xff1a;就是科幻中的平行宇宙&#xff0c;当你正在电脑面前学习C…

设计模式-单例模式

面向对象语言讲究的是万物皆对象。通常流程是先定义可实例化类&#xff0c;然后再通过各种不同的方式创建对象&#xff0c;因此类一般可以实例化出多个对象。但是实际项目开发时&#xff0c;我们还是希望保证项目运行时有且仅包含一个实例对象。这个需求场景的出发点包括但不限…