用命令模式设计一个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传输数据的协议规范,如下所示:

json 复制代码
{
	"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回调

kotlin 复制代码
interface JSInterface {

    fun callback(webView: WebView, params: String, successFunction: String, failFunction: String?)

}

让一个抽象类BaseJavaScriptHandler来实现这个接口

kotlin 复制代码
abstract class BaseJavaScriptHandler : JSInterface {

    override fun callback(
        webView: WebView,
        params: String,
        successFunction: String,
        failFunction: String?
    ) {

    }    
}

三、全局注册映射不同方法对应处理类

接着不同的方法,都通过继承这个BaseJavaScriptHandler来处理各自方法的回调。比如login方法对应的处理器LoginHandler

那么前端就只需要传一个login参数过来,就可以交给LoginHandler这个类去处理,这样Android的业务代码就可以和架构代码解耦了。

kotlin 复制代码
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 执行失败后回调给前端的方法名
       */
      @JavascriptInterface
      fun callNativeMethod(method: String, params: String, successFunction: String, failFunction: String) {
          
      }
    

    }

然后里面的实现可以通过用method方法名来解耦开来业务代码,不同的method方法对应用不同methodHandler类去解决单个方法需要执行的逻辑,这样就解耦开来了。

这样一来callNativeMethod方法的实现就好说了,如下所示:

kotlin 复制代码
		/**
     * @param method 前端调用Native端的方法名
     * @param params 前端透传来的参数
     * @param successFunction 执行成功后回调给前端的方法名
     * @param failFunction 执行失败后回调给前端的方法名
     */
    @JavascriptInterface
    fun 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()然后在里面传不同的方法名参数过来即可。

javascript 复制代码
function login() {
  // Call the Android login method
  JSBridge.callNativeMethod('login', JSON.stringify({account: username, password: password}), 			'onLoginSuccess', 'onLoginFail');
        }

六、所有代码

下面放出所有代码

HandlerManager.kt

kotlin 复制代码
import kotlin.collections.HashMap

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
    }

}

JSInterface.kt

ko 复制代码
import android.webkit.WebView

interface JSInterface {

    fun callback(webView: WebView, params: String, successFunction: String, failFunction: String?)

}

BaseJavaScriptHandler.kt

kotlin 复制代码
import android.os.Build
import android.util.Log
import android.webkit.WebView
import org.json.JSONObject

abstract 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.webviewapplication

import android.webkit.WebView
import android.widget.Toast
import org.json.JSONObject

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?) {
        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.Toast

class 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

kotlin 复制代码
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

kotlin 复制代码
import android.content.Context
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.widget.Toast

class JSBridge(private val context: Context, private val webView: WebView) {


    /**
     * @param method 前端调用Native端的方法名
     * @param params 前端透传来的参数
     * @param successFunction 执行成功后回调给前端的方法名
     * @param failFunction 执行失败后回调给前端的方法名
     */
    @JavascriptInterface
    fun 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

kotlin 复制代码
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.Toast

class BaseWebView @JvmOverloads constructor(
    context: Context, 
    attrs: AttributeSet? = null, 
    defStyleAttr: Int = 0
) : WebView(context, attrs, defStyleAttr) {

    init {
        setupWebView()
    }

    // 提供一份默认的webViewClient,同时提供自由注入业务的webViewClient
    private var webViewClient: WebViewClient = object : WebViewClient() {
        override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) {
            super.onPageStarted(view, url, favicon)
            // Handle page start
            Toast.makeText(context, "Page started: $url", Toast.LENGTH_SHORT).show()
        }

        override fun onPageFinished(view: WebView?, url: String?) {
            super.onPageFinished(view, url)
            // Handle page finish
            Toast.makeText(context, "Page finished: $url", Toast.LENGTH_SHORT).show()
        }

        override fun onReceivedError(
            view: WebView?,
            request: WebResourceRequest?,
            error: WebResourceError?
        ) {
            super.onReceivedError(view, request, error)
            // Handle error
            Toast.makeText(context, "Error: ${error?.description}", Toast.LENGTH_SHORT).show()
        }
    }

    @SuppressLint("SetJavaScriptEnabled")
    private fun setupWebView() {
        // Enable JavaScript
        settings.javaScriptEnabled = true

        // Enable DOM storage
        settings.domStorageEnabled = true

        // Set a WebViewClient to handle page navigation
        webViewClient = getWebViewClient()

        // Set a WebChromeClient to handle JavaScript dialogs, favicons, titles, and the progress
        webChromeClient = WebChromeClient()

        // Enable zoom controls
        settings.setSupportZoom(true)
        settings.builtInZoomControls = true
        settings.displayZoomControls = false

        // Enable caching
        settings.cacheMode = WebSettings.LOAD_DEFAULT
    }

    // Load a URL
    override fun loadUrl(url: String) {
        super.loadUrl(url)
    }

    // Load a URL with additional headers
    override fun loadUrl(url: String, additionalHttpHeaders: Map<String, String>) {
        super.loadUrl(url, additionalHttpHeaders)
    }

    // Lifecycle methods
    override fun onResume() {

    }

    override fun onPause() {

    }

    fun onDestroy() {
        // Clean up WebView
        clearHistory()
        freeMemory()
        destroy()
    }

    override fun setWebViewClient(client: WebViewClient) {
        this.webViewClient = client
    }

    override fun getWebViewClient() : WebViewClient {
        return webViewClient
    }
}

MainThreadUtils.kt

kotlin 复制代码
import android.os.Handler
import android.os.Looper

object 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

kotlin 复制代码
import android.annotation.SuppressLint
import android.os.Bundle
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity

class 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 = true
        webView.webViewClient = WebViewClient()
        webView.webChromeClient = WebChromeClient()

        // Add JSBridge interface
        webView.addJavascriptInterface(JSBridge(this, webView), "JSBridge")

        // Load the local HTML file
        webView.loadUrl("file:///android_asset/login.html")
    }
}

activity_main.xml

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">

    <WebView
        android: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

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 method
            JSBridge.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

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 method
            JSBridge.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>

最后运行截图:

用chrome://inspect/#devices还可以查看对应的JavaScript控制台输出的信息

代码目录结构

相关推荐
前端御书房2 小时前
前端PDF转图片技术调研实战指南:从踩坑到高可用方案的深度解析
前端·javascript
程序员黄同学3 小时前
请谈谈 Vue 中的响应式原理,如何实现?
前端·javascript·vue.js
宁波阿成4 小时前
vue3里组件的v-model:value与v-model的区别
前端·javascript·vue.js
柯腾啊5 小时前
VSCode 中使用 Snippets 设置常用代码块
开发语言·前端·javascript·ide·vscode·编辑器·代码片段
Jay丶萧邦5 小时前
el-select:有关多选,options选项值不包含绑定值的回显问题
javascript·vue.js·elementui
pixle05 小时前
Three.js 快速入门教程【一】开启你的 3D Web 开发之旅
前端·javascript·3d
我爱学习_zwj5 小时前
后台管理系统-月卡管理
javascript·vue.js·elementui
风浅月明6 小时前
[Android]如何判断当前APP是Debug还是Release环境?
android
freflying11196 小时前
使用jenkins构建Android+Flutter项目依赖自动升级带来兼容性问题及Jenkins构建速度慢问题解决
android·flutter·jenkins
录大大i6 小时前
HtML之JavaScript BOM编程
前端·javascript·html