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.hqwc.cn/news/21712.html

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

相关文章

内容过多,超出部分以省略号“...”显示

效果图如图所示&#xff1a; 1.第一种实现方法&#xff0c;使用纯css实现&#xff08;ps&#xff1a;此方式必须给元素设置宽度&#xff0c;否则可能无效果&#xff09;&#xff0c;代码如下&#xff1a; html代码 <!-- 超过长度&#xff0c;用省略号实现&#xff0c;css的…

【ONE·Linux || 地址空间与进程控制(一)】

总言 进程地址空间和进程控制相关介绍。 文章目录 总言1、进程地址空间1.1、程序地址空间初识1.1.1、介绍程序地址空间划分及地址空间初步验证1.1.2、地址空间再次综述演示1.1.3、两个补充问题&#xff1a; 1.2、地址空间是什么1.2.1、阶段认识一&#xff1a;故事引入1.2.2、阶…

springboot乒乓球预约管理系统

开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven…

es下载历史的tar文件

第一步进入官网找到历史版本 第二步复制历史版本名称组合成下面的链接 直接get访问下载。如下链接所示只需要修改7.3.0这个版本号 https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.3.0-linux-x86_64.tar.gz

刚体三维运动学【旋转矩阵】【欧拉角】【四元素】

一些概念 轴角法、旋转矩阵、欧拉角、四元数主要用于&#xff1a;向量的旋转、坐标系之间的转换、角位移的计算、方位的平滑插值计算。坐标系的旋转一共有三种表示方法&#xff1a;旋转矩阵、欧拉角和四元数。一般指地面系&#xff08;世界系&#xff09;和机体系之间的旋转关…

Linux —— 进程管理

目录 一&#xff0c;进程介绍 二&#xff0c;进程使用 进程查看 通过系统调用获取进程标识符 通过系统调用创建进程 fork 一&#xff0c;进程介绍 进程是正在执行的程序或命令&#xff0c;每个进程都是一个运行的实体或程序的执行实例&#xff0c;有自己的地址空间&#x…

【Excel】excel多个单元格的内容合并到一个单元格,并使用分隔符

方法一&#xff1a;使用连接符 & 左键单击选中“D2”单元格&#xff0c;在D2单元格中输入公式“A2&B2&C2”&#xff0c;按“Enter”即可实现数据合并。 ------如果想连接的时候&#xff0c;中间加分隔符&#xff0c;可以使用&#xff1a;公式A2&"&#xf…

FPGA学习——PWM实现呼吸流水灯(附源码)

文章目录 一、PWM简介1.1 PWM定义1.2 PWM参数 二、Verilog实现PWM呼吸灯三、实现效果四、总结 一、PWM简介 1.1 PWM定义 PWM是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用&#xff0c;方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM信号…

AI Is the New Power

这个题目纯粹是为了博眼球&#xff0c;因为吴恩达有个题目是AI Is the New Electricity。&#xff1a;&#xff09;但是我想AI确实是为我们这些企业信息化顾问顾问赋予了新的力量&#xff0c;在我们的职业生涯中开辟了新的可能性。 在几周前的文章中&#xff0c;我们提到“终点…

Harnessing the Power of LLMs in Practice: A Survey on ChatGPT and Beyond

LLM的系列文章&#xff0c;针对《Harnessing the Power of LLMs in Practice: A Survey on ChatGPT and Beyond》的翻译。 在实践中驾驭LLM的力量——ChatGPT及其后的研究综述 摘要1 引言2 模型实用指南2.1 BERT风格的语言模型&#xff1a;编码器-解码器或仅编码器2.2 GPT风格…

WEIQ自动登录实现

文章目录 声明目标网址password加密分析代码实现声明 本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请私信我立即删除! 目标网址 aHR0cHM6Ly93d3cud2VpcS5jb20vpassword加密分析 研究一下登录加密逻辑,随便…

js小写金额转大写 自动转换

// 小写转为大写convertCurrency(money) {var cnNums [零, 壹, 贰, 叁, 肆, 伍, 陆, 柒, 捌, 玖]var cnIntRadice [, 拾, 佰, 仟]var cnIntUnits [, 万, 亿, 兆]var cnDecUnits [角, 分, 毫, 厘]// var cnInteger 整var cnIntLast 元var maxNum 999999999999999.9999var…