用命令模式設計一個JSBridge用于JavaScript與Android交互通信

用命令模式設計一個JSBridge用于JavaScript與Android交互通信

在開發APP的過程中,通常會遇到Android需要與H5頁面互相傳遞數據的情況,而Android與H5交互的容器就是WebView。

因此要想設計一個高可用的 J S B r i d g e JSBridge JSBridge,不妨可以參考下述示例:

一、傳輸協議規范

設計一套用于 A n d r o i d Android Android端與 J a v a S c r i p t JavaScript JavaScript傳輸數據的協議規范,如下所示:

{"code": "1000001","msg": "調用成功","content": {"model": "NOH-AL00","brand": "HUAWEI"}
}

其中

  • code 字段用來表示調用的狀態碼
  • msg 字段用來表示調用信息
  • content 字段用來傳輸數據

既然是要設計到Android與JavaScript兩個交互,就必然會涉及

  • Android端傳輸數據給JavaScript

    • 一般是通過 w e b V i e w . e v a l u a t e J a v a s c r i p t ( j a v a S c r i p t C o d e , n u l l ) webView.evaluateJavascript(javaScriptCode, null) webView.evaluateJavascript(javaScriptCode,null)
  • JavaScript端傳輸數據給Android

    • J S B r i d g e . c a l l N a t i v e M e t h o d ( ) JSBridge.callNativeMethod() JSBridge.callNativeMethod()

      其中要求Android端會有個統一入口,方法名叫做callNativeMethod ,然后會暴露一個JavaScript的入口webView.addJavascriptInterface(JSBridge(this, webView), “JSBridge”)

二、Android端接口

設計一個JSInterface接口,來執行Javascript調用Android回調

interface JSInterface {fun callback(webView: WebView, params: String, successFunction: String, failFunction: String?)}

讓一個抽象類BaseJavaScriptHandler來實現這個接口

abstract class BaseJavaScriptHandler : JSInterface {override fun callback(webView: WebView,params: String,successFunction: String,failFunction: String?) {}    
}

三、全局注冊映射不同方法對應處理類

接著不同的方法,都通過繼承這個BaseJavaScriptHandler來處理各自方法的回調。比如login方法對應的處理器LoginHandler

那么前端就只需要傳一個login參數過來,就可以交給LoginHandler這個類去處理,這樣Android的業務代碼就可以和架構代碼解耦了。

class LoginHandler : BaseJavaScriptHandler() {companion object {const val KEY_ACCOUNT = "account"const val KEY_PASSWORD = "password"}override fun callback(webView: WebView,params: String,successFunction: String,failFunction: String?) {login(webView, params, successFunction, failFunction)}private fun login(webView: WebView,params: String,successFunction: String,failFunction: String?) {}}

那么接下來如何讓不同的方法都映射到不同的類名里的callback方法里去呢?

答案:通過map保存對應的方法名映射到類名的關系

然后對外暴露getJavaScriptHandler方法,來獲取對應的Handler實例對象來運行callback接口

object HandlerManager {const val TAG = "HandlerManager"private val map = HashMap<String, Class<out BaseJavaScriptHandler>>()fun registerJavaScriptHandler() {register(JSBridgeConstants.METHOD_NAME_LOGIN, LoginHandler::class.java)register(JSBridgeConstants.METHOD_NAME_SHOW_TOAST, ShowToastHandler::class.java)}fun getJavaScriptHandler(methodName: String) : Class<out BaseJavaScriptHandler>? {return if (map.containsKey(methodName)) {map[methodName]} else {NoSuchMethodHandler::class.java}}private fun register(methodName: String, classObject: Class<out BaseJavaScriptHandler>) {map[methodName] = classObject}}

四、統一分發不同方法執行

由于通常前端 J a v a S c r i p t JavaScript JavaScript A n d r o i d Android Android交互會有多個不同的方法調用,因此我們需要設計一個統一全局調用的收口地方,然后不同的方法通過不同的參數來區分即可。

Android端加上一個@JavascriptInterface注解,用于收斂一個與js交互的入口。

這樣設計的好處是:

  • 可以統一埋點統計Javascript調用Android代碼的次數
  • 收斂一個入口,找代碼方便,代碼簡潔解耦清晰
class JSBridge(private val context: Context, private val webView: WebView) {/*** @param method 前端調用Native端的方法名* @param params 前端透傳來的參數* @param successFunction 執行成功后回調給前端的方法名* @param failFunction 執行失敗后回調給前端的方法名*/@JavascriptInterfacefun callNativeMethod(method: String, params: String, successFunction: String, failFunction: String) {}
} 

然后里面的實現可以通過用method方法名來解耦開來業務代碼,不同的method方法對應用不同methodHandler類去解決單個方法需要執行的邏輯,這樣就解耦開來了。

這樣一來callNativeMethod方法的實現就好說了,如下所示:

		/*** @param method 前端調用Native端的方法名* @param params 前端透傳來的參數* @param successFunction 執行成功后回調給前端的方法名* @param failFunction 執行失敗后回調給前端的方法名*/@JavascriptInterfacefun callNativeMethod(method: String, params: String, successFunction: String, failFunction: String) {val javaScriptHandler = HandlerManager.getJavaScriptHandler(method)// 如果找到對應的 handler,則執行處理javaScriptHandler?.let { handler ->// 生成對應handler的實例對象                    val handlerInstance = handler.newInstance()// 觸發對應handler的回調                    handlerInstance.callback(webView, params, successFunction, failFunction)} ?: run {// 如果沒有找到對應的 handler,可以打印日志或顯示提示Toast.makeText(context, "未找到對應的處理方法: $method", Toast.LENGTH_SHORT).show()}}

只需要在實例化全局WebView的時候,去暴露Javascript接口實例對象即可,如下所示

// 全局注冊
HandlerManager.registerJavaScriptHandler()val webView: WebView = findViewById(R.id.web_container)
webView.settings.javaScriptEnabled = true
webView.webViewClient = WebViewClient()
webView.webChromeClient = WebChromeClient()// Add JSBridge interface
webView.addJavascriptInterface(JSBridge(this, webView), "JSBridge")
webView.loadUrl("file:///android_asset/index.html"))

五、前端調用

這樣前端調用Android端的方法就很簡單了,通過 J S B r i d g e . c a l l N a t i v e M e t h o d ( ) JSBridge.callNativeMethod() JSBridge.callNativeMethod()然后在里面傳不同的方法名參數過來即可。

function login() {// Call the Android login methodJSBridge.callNativeMethod('login', JSON.stringify({account: username, password: password}), 			'onLoginSuccess', 'onLoginFail');}

六、所有代碼

下面放出所有代碼

HandlerManager.kt

import kotlin.collections.HashMapobject HandlerManager {const val TAG = "HandlerManager"private val map = HashMap<String, Class<out BaseJavaScriptHandler>>()fun registerJavaScriptHandler() {register(JSBridgeConstants.METHOD_NAME_LOGIN, LoginHandler::class.java)register(JSBridgeConstants.METHOD_NAME_SHOW_TOAST, ShowToastHandler::class.java)}fun getJavaScriptHandler(methodName: String) : Class<out BaseJavaScriptHandler>? {return if (map.containsKey(methodName)) {map[methodName]} else {NoSuchMethodHandler::class.java}}private fun register(methodName: String, classObject: Class<out BaseJavaScriptHandler>) {map[methodName] = classObject}}

JSInterface.kt

import android.webkit.WebViewinterface JSInterface {fun callback(webView: WebView, params: String, successFunction: String, failFunction: String?)}

BaseJavaScriptHandler.kt

import android.os.Build
import android.util.Log
import android.webkit.WebView
import org.json.JSONObjectabstract class BaseJavaScriptHandler : JSInterface {companion object {const val TAG = "BaseJavaScriptHandler"}override fun callback(webView: WebView,params: String,successFunction: String,failFunction: String?) {}fun callbackToJavaScript(webView: WebView, callbackMethod: String?, callbackParams: String?) {if (callbackMethod == null) {return}var javaScriptCode = if (callbackParams != null) {"$callbackMethod($callbackParams)"} else {"$callbackMethod()"}Log.i(TAG, "===> javaScriptCode is $javaScriptCode")MainThreadUtils.runOnMainThread(runnable = Runnable {webView.evaluateJavascript(javaScriptCode, null)})}fun getCallbackParams(code: String?, msg: String?, content: String?) : String {val params = JSONObject().apply {code?.let {put(JSBridgeConstants.KEY_CODE, code)}msg?.let {put(JSBridgeConstants.KEY_MSG, msg)}if (content == null) {put(JSBridgeConstants.KEY_CONTENT, getExtraParams().toString())} else {put(JSBridgeConstants.KEY_CONTENT, content)}}return params.toString()}fun getExtraParams(): JSONObject {val jsonObject = JSONObject().apply {put(JSBridgeConstants.KEY_BRAND, Build.BRAND)put(JSBridgeConstants.KEY_MODEL, Build.MODEL)}return jsonObject}
}

LoginHandler.kt

package com.check.webviewapplicationimport android.webkit.WebView
import android.widget.Toast
import org.json.JSONObjectclass LoginHandler : BaseJavaScriptHandler() {companion object {const val KEY_ACCOUNT = "account"const val KEY_PASSWORD = "password"}override fun callback(webView: WebView,params: String,successFunction: String,failFunction: String?) {login(webView, params, successFunction, failFunction)}private fun login(webView: WebView,params: String,successFunction: String,failFunction: String?) {val paramsObject = JSONObject(params)val account: String = paramsObject.opt(KEY_ACCOUNT) as? String ?: ""val password: String = paramsObject.get(KEY_PASSWORD) as? String ?: ""val isSuccess = checkValid(account, password)if (isSuccess) {showToast(webView, "登錄成功")val callbackParams = getCallbackParams(JSBridgeConstants.CODE_SUCCESS,JSBridgeConstants.MSG_SUCCESS,getExtraParams().toString())callbackToJavaScript(webView, successFunction, callbackParams)} else {showToast(webView, "登錄失敗")val callbackParams = getCallbackParams(JSBridgeConstants.CODE_FAILURE,JSBridgeConstants.MSG_FAILURE,getExtraParams().toString())callbackToJavaScript(webView, failFunction, callbackParams)}}private fun checkValid(account: String, password: String) : Boolean {// 模擬賬號檢驗流程,假設只有賬號是123,密碼是456的才可以檢驗通過return "123" == account && "456" == password}private fun showToast(webView: WebView, msg: String) {webView.context?.let {Toast.makeText(webView.context, msg, Toast.LENGTH_SHORT).show()}}}

ShowToastHandler.kt

import android.webkit.WebView
import android.widget.Toastclass ShowToastHandler : BaseJavaScriptHandler() {override fun callback(webView: WebView,params: String,successFunction: String,failFunction: String?) {webView.context?.let {Toast.makeText(webView.context, JSBridgeConstants.METHOD_NAME_SHOW_TOAST, Toast.LENGTH_SHORT).show()}val callbackParams =getCallbackParams(JSBridgeConstants.CODE_SUCCESS, JSBridgeConstants.MSG_SUCCESS, null)callbackToJavaScript(webView, successFunction, callbackParams)}}

JSBridgeConstants.kt

class JSBridgeConstants {companion object {const val METHOD_NAME_LOGIN = "login"const val METHOD_NAME_SHOW_TOAST = "showToast"const val MSG_SUCCESS =  "此方法執行成功"const val MSG_FAILURE =  "此方法執行失敗"const val CODE_SUCCESS = "1"const val CODE_FAILURE = "0"const val KEY_CODE = "code"const val KEY_MSG = "msg"const val KEY_CONTENT = "content"const val VALUE_SUCCESS = "1"const val VALUE_FAILURE = "0"const val KEY_MODEL = "model"const val KEY_BRAND = "brand"}}

JSBridge.kt

import android.content.Context
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.widget.Toastclass JSBridge(private val context: Context, private val webView: WebView) {/*** @param method 前端調用Native端的方法名* @param params 前端透傳來的參數* @param successFunction 執行成功后回調給前端的方法名* @param failFunction 執行失敗后回調給前端的方法名*/@JavascriptInterfacefun callNativeMethod(method: String, params: String, successFunction: String, failFunction: String) {val javaScriptHandler = HandlerManager.getJavaScriptHandler(method)// 如果找到對應的 handler,則執行處理javaScriptHandler?.let { handler ->val handlerInstance = handler.newInstance()handlerInstance.callback(webView, params, successFunction, failFunction)} ?: run {// 如果沒有找到對應的 handler,可以打印日志或顯示提示Toast.makeText(context, "未找到對應的處理方法: $method", Toast.LENGTH_SHORT).show()}}
} 

BaseWebView.kt

import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.webkit.WebChromeClient
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toastclass BaseWebView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr) {init {setupWebView()}// 提供一份默認的webViewClient,同時提供自由注入業務的webViewClientprivate var webViewClient: WebViewClient = object : WebViewClient() {override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) {super.onPageStarted(view, url, favicon)// Handle page startToast.makeText(context, "Page started: $url", Toast.LENGTH_SHORT).show()}override fun onPageFinished(view: WebView?, url: String?) {super.onPageFinished(view, url)// Handle page finishToast.makeText(context, "Page finished: $url", Toast.LENGTH_SHORT).show()}override fun onReceivedError(view: WebView?,request: WebResourceRequest?,error: WebResourceError?) {super.onReceivedError(view, request, error)// Handle errorToast.makeText(context, "Error: ${error?.description}", Toast.LENGTH_SHORT).show()}}@SuppressLint("SetJavaScriptEnabled")private fun setupWebView() {// Enable JavaScriptsettings.javaScriptEnabled = true// Enable DOM storagesettings.domStorageEnabled = true// Set a WebViewClient to handle page navigationwebViewClient = getWebViewClient()// Set a WebChromeClient to handle JavaScript dialogs, favicons, titles, and the progresswebChromeClient = WebChromeClient()// Enable zoom controlssettings.setSupportZoom(true)settings.builtInZoomControls = truesettings.displayZoomControls = false// Enable cachingsettings.cacheMode = WebSettings.LOAD_DEFAULT}// Load a URLoverride fun loadUrl(url: String) {super.loadUrl(url)}// Load a URL with additional headersoverride fun loadUrl(url: String, additionalHttpHeaders: Map<String, String>) {super.loadUrl(url, additionalHttpHeaders)}// Lifecycle methodsoverride fun onResume() {}override fun onPause() {}fun onDestroy() {// Clean up WebViewclearHistory()freeMemory()destroy()}override fun setWebViewClient(client: WebViewClient) {this.webViewClient = client}override fun getWebViewClient() : WebViewClient {return webViewClient}
}

MainThreadUtils.kt

import android.os.Handler
import android.os.Looperobject MainThreadUtils {private val mainHandler = Handler(Looper.getMainLooper())/*** 判斷當前是否在主線程*/fun isMainThread(): Boolean {return Looper.getMainLooper().thread === Thread.currentThread()}/*** 在主線程執行代碼塊* @param runnable 需要執行的代碼塊*/fun runOnMainThread(runnable: Runnable) {if (isMainThread()) {runnable.run()} else {mainHandler.post(runnable)}}/*** 在主線程執行代碼塊(使用 lambda 表達式)* @param block 需要執行的代碼塊*/fun runOnMainThread(block: () -> Unit) {if (isMainThread()) {block.invoke()} else {mainHandler.post { block.invoke() }}}/*** 延遲在主線程執行代碼塊* @param delayMillis 延遲時間(毫秒)* @param block 需要執行的代碼塊*/fun runOnMainThreadDelayed(delayMillis: Long, block: () -> Unit) {mainHandler.postDelayed({ block.invoke() }, delayMillis)}
}

MainActivity.kt

import android.annotation.SuppressLint
import android.os.Bundle
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivityclass MainActivity : AppCompatActivity() {@SuppressLint("SetJavaScriptEnabled")override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 全局注冊HandlerManager.registerJavaScriptHandler()val webView: WebView = findViewById(R.id.web_container)webView.settings.javaScriptEnabled = truewebView.webViewClient = WebViewClient()webView.webChromeClient = WebChromeClient()// Add JSBridge interfacewebView.addJavascriptInterface(JSBridge(this, webView), "JSBridge")// Load the local HTML filewebView.loadUrl("file:///android_asset/login.html")}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><WebViewandroid:id="@+id/web_container"android:layout_width="match_parent"android:layout_height="600dp"android:text="Hello World!"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"/></androidx.constraintlayout.widget.ConstraintLayout>

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Login</title><style>body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;display: flex;justify-content: center;align-items: center;height: 100vh;margin: 0;background-color: #e9ecef;}.login-container {background-color: #fff;padding: 30px;border-radius: 10px;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);width: 320px;text-align: center;}.login-container input,.login-container button {display: block;width: 100%;margin-bottom: 15px;padding: 12px;border-radius: 5px;font-size: 16px;box-sizing: border-box;}.login-container input {border: 1px solid #ddd;}.login-container button {background-color: #007BFF;color: white;border: none;cursor: pointer;transition: background-color 0.3s;}.login-container button:hover {background-color: #0056b3;}.message {margin-top: 15px;font-size: 14px;color: green;}.error {color: red;}</style>
</head>
<body><div class="login-container"><input type="text" id="username" placeholder="Username"><input type="password" id="password" placeholder="Password"><button onclick="login()">Login</button><button onclick="showToast()">ShowToast</button><div id="message" class="message"></div></div><script>function login() {var username = document.getElementById('username').value;var password = document.getElementById('password').value;// Call the Android login methodJSBridge.callNativeMethod('login', JSON.stringify({account: username, password: password}), 'onLoginSuccess', 'onLoginFail');}function showToast() {JSBridge.callNativeMethod('showToast', '', '', '');}function onLoginSuccess(response) {console.log("Raw response:", response);var messageDiv = document.getElementById('message');try {// 先將 response 轉換為 JSON 字符串const jsonString = JSON.stringify(response);console.log("JSON string:", jsonString);// 然后解析為對象const params = JSON.parse(jsonString);console.log("Parsed params:", params);if (params.content) {const content = JSON.parse(params.content);console.log("Parsed content:", content);messageDiv.textContent = `Login successful! Brand: ${content.brand}, Model: ${content.model}`;} else {messageDiv.textContent = "Login successful! " + params.msg;}} catch (e) {console.error("Error parsing response:", e);messageDiv.textContent = "Login failed: " + e.message;}messageDiv.classList.remove('error');}function onLoginFail(response) {var messageDiv = document.getElementById('message');messageDiv.textContent = "Login failed!" + response;messageDiv.classList.add('error');}</script>
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Login</title><style>body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;display: flex;justify-content: center;align-items: flex-end;height: 100vh;margin: 0;background-color: #e9ecef;}.login-container {background-color: #fff;padding: 30px;border-radius: 10px;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);width: 320px;text-align: center;margin-bottom: 20px;}.login-container input,.login-container button {display: block;width: 100%;margin-bottom: 15px;padding: 12px;border-radius: 5px;font-size: 16px;box-sizing: border-box;}.login-container input {border: 1px solid #ddd;}.login-container button {background-color: #007BFF;color: white;border: none;cursor: pointer;transition: background-color 0.3s;}.login-container button:hover {background-color: #0056b3;}.message {margin-top: 15px;font-size: 14px;color: green;}.error {color: red;}</style>
</head>
<body><div class="login-container"><input type="text" id="username" placeholder="Username"><input type="password" id="password" placeholder="Password"><button onclick="login()">Login</button><button onclick="showToast()">ShowToast</button><div id="message" class="message"></div></div><script>function login() {var username = document.getElementById('username').value;var password = document.getElementById('password').value;// Call the Android login methodJSBridge.callNativeMethod('login', JSON.stringify({account: username, password: password}), 'onLoginSuccess', 'onLoginFail');}function showToast() {JSBridge.callNativeMethod('showToast', '', '', '');}function onLoginSuccess(response) {console.log("Raw response:", response);var messageDiv = document.getElementById('message');try {// 先將 response 轉換為 JSON 字符串const jsonString = JSON.stringify(response);console.log("JSON string:", jsonString);// 然后解析為對象const params = JSON.parse(jsonString);console.log("Parsed params:", params);if (params.content) {const content = JSON.parse(params.content);console.log("Parsed content:", content);messageDiv.textContent = `Login successful! Brand: ${content.brand}, Model: ${content.model}`;} else {messageDiv.textContent = "Login successful! " + params.msg;}} catch (e) {console.error("Error parsing response:", e);messageDiv.textContent = "Login failed: " + e.message;}messageDiv.classList.remove('error');}function onLoginFail(response) {var messageDiv = document.getElementById('message');messageDiv.textContent = "Login failed!" + response;messageDiv.classList.add('error');}</script>
</body>
</html>

最后運行截圖:

image-20250216224947804

用chrome://inspect/#devices還可以查看對應的JavaScript控制臺輸出的信息

image-20250216225112588

代碼目錄結構

image-20250216224328451

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/70366.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/70366.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/70366.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ModuleNotFoundError: No module named ‘timm.optim.novogr兩種解決方法

運行報錯 from timm.optim.novograd import NovoGradModuleNotFoundError: No module named ‘timm.optim.novograd’。 問題原因 timm版本過高&#xff0c;novograd函數已被拋棄。 解決辦法 方法1&#xff1a;安裝更低版本的timm pip install timm0.4.12方法2&#xff1a…

DeepSeek 本地部署指南:從零開始搭建 AI 搜索工具

1. 引言 背景介紹 DeepSeek 是一款基于 AI 的搜索工具&#xff0c;能夠高效處理海量數據&#xff0c;提供精準的搜索結果。它結合了 Ollama 的模型管理能力&#xff0c;使得部署更加便捷。 為什么選擇本地部署 本地部署可以確保數據隱私&#xff0c;避免云端傳輸的風險&…

昇騰DeepSeek模型部署優秀實踐及FAQ

2024年12月26日&#xff0c;DeepSeek-V3橫空出世&#xff0c;以其卓越性能備受矚目。該模型發布即支持昇騰&#xff0c;用戶可在昇騰硬件和MindIE推理引擎上實現高效推理&#xff0c;但在實際操作中&#xff0c;部署流程與常見問題困擾著不少開發者。本文將為你詳細闡述昇騰 De…

vscode復制到下一行

linux中默認快捷鍵是ctrl shift alt down/up 但是在vscode中無法使用&#xff0c;應該是被其他的東西綁定了&#xff0c;經測試&#xff0c;可以使用windows下的快捷鍵shift alt down/up { “key”: “shiftaltdown”, “command”: “editor.action.copyLinesDownAction”…

網絡爬蟲學習:借助DeepSeek完善爬蟲軟件,實現模擬鼠標右鍵點擊,將鏈接另存為本地文件

一、前言 最近幾個月里&#xff0c;我一直在學習網絡爬蟲方面的知識&#xff0c;每有收獲都會將所得整理成文發布&#xff0c;不知不覺已經發了7篇日志了&#xff1a; 網絡爬蟲學習&#xff1a;從百度搜索結果抓取標題、鏈接、內容&#xff0c;并保存到xlsx文件中 網絡爬蟲學…

Arduino 第十六章:pir紅外人體傳感器練習

Arduino 第十六章&#xff1a;PIR 傳感器練習 一、引言 在 Arduino 的眾多有趣項目中&#xff0c;傳感器的應用是非常重要的一部分。今天我們要學習的主角是 PIR&#xff08;被動紅外&#xff09;傳感器。PIR 傳感器能夠檢測人體發出的紅外線&#xff0c;常用于安防系統、自動…

CV -- YOLOv8 圖像分割(GPU環境)

目錄 參考視頻&#xff1a; 標注 JSON轉為TXT 訓練 驗證 參考視頻&#xff1a; 使用 Yolov8 自定義數據集進行圖像分割_嗶哩嗶哩_bilibili 標注 數據集&#xff1a; 我使用的是一些蘋果數據集&#xff0c;可以在我的csdn資源中下載&#xff1a; https://download.csdn.net/do…

深入理解 lua_KFunction 和 lua_CFunction

在 Lua C API 中,lua_KFunction 和 lua_CFunction 是兩個核心概念,尤其在處理協程和 C 函數擴展時扮演著至關重要的角色。lua_CFunction 作為一種 C 函數類型,允許開發者將 C 函數注冊到 Lua 環境中,使得這些 C 函數可以在 Lua 腳本中被調用,進而實現 Lua 的功能擴展。而 …

基于微信小程序的電影院訂票選座系統的設計與實現,SSM+Vue+畢業論文+開題報告+任務書+指導搭建視頻

本系統包含用戶、管理員兩個角色。 用戶角色&#xff1a;注冊登錄、查看首頁電影信息推薦、查看電影詳情并進行收藏預定、查看電影資訊、在線客服、管理個人訂單等。 管理員角色&#xff1a;登錄后臺、管理電影類型、管理放映廳信息、管理電影信息、管理用戶信息、管理訂單等。…

【Linux網絡編程】應用層協議HTTP(請求方法,狀態碼,重定向,cookie,session)

&#x1f381;個人主頁&#xff1a;我們的五年 &#x1f50d;系列專欄&#xff1a;Linux網絡編程 &#x1f337;追光的人&#xff0c;終會萬丈光芒 &#x1f389;歡迎大家點贊&#x1f44d;評論&#x1f4dd;收藏?文章 ? Linux網絡編程筆記&#xff1a; https://blog.cs…

Vue3 打造 Windows 桌面個性高效組件工具

軟件介紹 Widgets 這款基于 Vue3 構建的開源 Windows 桌面小部件工具超實用。 其多樣化組件庫涵蓋超 20 種&#xff0c;從倒計時、打工進度等實用工具&#xff0c;到抖音熱榜等實時資訊組件應有盡有&#xff0c;各組件獨立運行&#xff0c;滿足多場景需求。 高度自定義布局支持…

Tailwind CSS 和 UnoCSS簡單比較

一、Tailwind CSS 和 UnoCSS簡介 Tailwind CSS 和 UnoCSS 都是流行的原子化 CSS 框架&#xff0c;它們有很多相似之處&#xff0c;但也存在一些明顯的區別&#xff0c;以下從多個方面對它們進行比較&#xff1a; 1. 基本概念和原理 Tailwind CSS 是最早提出并推動原子化 CSS…

什么是語料清洗、預訓練、指令微調、強化學習、內容安全; 什么是megatron,deepspeed,vllm推理加速框架

什么是語料清洗、預訓練、指令微調、強化學習、內容安全 目錄 什么是語料清洗、預訓練、指令微調、強化學習、內容安全語料清洗預訓練指令微調強化學習內容安全什么是megatron,deepspeed,vllm推理加速框架語料清洗 語料清洗是對原始文本數據進行處理的過程,旨在去除數據中的…

C++(23):lambda可以省略()

C越來越多的使用了lambda&#xff0c;C23也進一步的放寬了對lambda的限制&#xff0c;這一次&#xff0c;如果lambda沒有參數列表&#xff0c;那么可以直接省略掉()&#xff1a; #include <iostream> using namespace std;void func() {auto f []{cout<<"in…

自制操作系統分享第四天

今天要做什么&#xff1f; &#xff08;1&#xff09;分享 我們上面已經說過INT 0x13這種指令&#xff08;調用BIOS的INT指令&#xff09;&#xff0c;我們知道這是調用BIOS的0x13函數&#xff0c;但還不明白它到底是干什么用的&#xff1a; AH 0x02;# 讀盤 AH 0x03;# 寫…

DeepSeek預測25考研分數線

25考研分數馬上要出了。 目前&#xff0c;多所大學已經陸續給出了分數查分時間&#xff0c;綜合往年情況來看&#xff0c;每年的查分時間一般集中在2月底。 等待出成績的日子&#xff0c;學子們的心情是萬分焦急&#xff0c;小編用最近爆火的“活人感”十足的DeepSeek幫大家預…

Android 動態加入Activity 時 manifest 注冊報錯解決。使用manifestPlaceholders 占位

需求如下&#xff1a; 項目 測試demo 有多個渠道&#xff0c;部分渠道包含支付功能&#xff0c;在主測試代碼外&#xff0c;需要一個單獨 Activity 調用測試代碼。 MainActivityPayActivity渠道A包含不包含渠道B包含包含 因為支付功能需要引入對應的 moudule&#xff0c;因此…

FRRouting配置與OSPF介紹,配置,命令,bfd算法:

文章目錄 1、frrouting的配置&#xff1a;2、ospf2.1、檢測和維護鄰居關系2.2、ospfDR和BDR2.3、odpf鄰居表2.4、ospf常用命令2.5、bfd配置 1、frrouting的配置&#xff1a; sudo service zebra start sudo service ospfd start telnet localhost 2604 en configure termina…

Perplexity 開源DeepSeek-R1 模型新版本 R1-1776

引言 在人工智能領域&#xff0c;模型的更新迭代如同科技界的時尚潮流&#xff0c;不斷推陳出新。今天&#xff0c;我們要介紹的是Perplexity AI開源的新版推理模型——DeepSeek-R1 1776。這個版本不僅繼承了前代的強大性能&#xff0c;還在公正性和準確性上進行了顯著提升。那…

關系中出現這10個信號,離分手就不遠了(愛情友情都適用)

親密關系的隱形裂痕 在一個陽光明媚卻略顯蕭瑟的午后&#xff0c;咖啡杯里的咖啡已經涼透。小李盯著手機屏幕&#xff0c;那些曾經熱烈的對話記錄現在看起來如此陌生&#xff0c;仿佛隔著一層薄薄的霧。她終于意識到&#xff0c;這段關系已經悄然走向盡頭。 親密關系是一場精心…