通过JSBridge与客户端通讯方式与原理,看这篇文章就够了

重点介绍了 做面混合应用和移动端混合应用 通过 JSBridge 实现 前端和客户端通讯的机制和原理,详细讲解了实现方式

一 CEF (Chromium Embedded Framework) 中的 JSBridge 实现原理

CEF (Chromium Embedded Framework) 通过 JSBridge 实现 C++ 和 JavaScript 之间的相互调用和通信,主要基于以下几种机制:

1. 从 C++ 调用 JavaScript

基本方式

cpp

scss 复制代码
CefRefPtr<CefFrame> frame = browser->GetMainFrame();
frame->ExecuteJavaScript("alert('Hello from C++');", frame->GetURL(), 0);

更复杂的数据传递

可以通过 JSON 格式传递复杂数据:

cpp

ini 复制代码
std::string js_code = "myJSFunction(" + GenerateJSONData() + ");";
frame->ExecuteJavaScript(js_code, frame->GetURL(), 0);

2. 从 JavaScript 调用 C++

通过绑定 V8 扩展

  1. 创建处理类 :继承 CefV8Handler

cpp

kotlin 复制代码
class MyV8Handler : public CefV8Handler {
public:
  virtual bool Execute(const CefString& name,
                       CefRefPtr<CefV8Value> object,
                       const CefV8ValueList& arguments,
                       CefRefPtr<CefV8Value>& retval,
                       CefString& exception) override {
    if (name == "myFunction") {
      // 处理来自JS的调用
      return true;
    }
    return false;
  }
};
  1. 绑定到 JavaScript 上下文

cpp

php 复制代码
void OnContextCreated(CefRefPtr<CefBrowser> browser,
                     CefRefPtr<CefFrame> frame,
                     CefRefPtr<CefV8Context> context) {
  CefRefPtr<CefV8Value> object = CefV8Value::CreateObject(nullptr, nullptr);
  object->SetValue("myFunction", 
                  CefV8Value::CreateFunction("myFunction", new MyV8Handler()),
                  V8_PROPERTY_ATTRIBUTE_NONE);
  
  context->GetGlobal()->SetValue("myObject", object, V8_PROPERTY_ATTRIBUTE_NONE);
}

通过 Window Binding (更简单的方式)

cpp

rust 复制代码
CefRefPtr<CefV8Value> obj = CefV8Value::CreateObject(nullptr);
obj->SetValue("nativeFunc", 
             CefV8Value::CreateFunction("nativeFunc", new MyHandler()),
             V8_PROPERTY_ATTRIBUTE_NONE);

context->GetGlobal()->SetValue("nativeObj", obj, V8_PROPERTY_ATTRIBUTE_NONE);

3. 异步通信机制

使用 CefProcessMessage

  1. 从 JavaScript 发送消息到 C++

javascript

javascript 复制代码
// JavaScript 发送消息
window.cefQuery({
  request: 'my_request',
  onSuccess: function(response) {},
  onFailure: function(error_code, error_message) {}
});
  1. C++ 端处理

cpp

swift 复制代码
bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
                             CefRefPtr<CefFrame> frame,
                             CefProcessId source_process,
                             CefRefPtr<CefProcessMessage> message) {
  if (message->GetName() == "cef_query") {
    CefRefPtr<CefListValue> args = message->GetArgumentList();
    // 处理请求并返回响应
    return true;
  }
  return false;
}

4. 双向通信的最佳实践

推荐架构

  1. 在 C++ 中创建桥梁对象

cpp

arduino 复制代码
class JSBridge : public CefV8Handler {
  // 实现Execute方法处理JS调用
};

// 在上下文创建时注入
void OnContextCreated(...) {
  CefRefPtr<CefV8Value> bridge = CefV8Value::CreateObject(nullptr);
  bridge->SetValue("callNative", 
                  CefV8Value::CreateFunction("callNative", new JSBridge()),
                  V8_PROPERTY_ATTRIBUTE_NONE);
  context->GetGlobal()->SetValue("nativeBridge", bridge);
}
  1. JavaScript 端封装

javascript

javascript 复制代码
class NativeBridge {
  static call(method, data) {
    return new Promise((resolve, reject) => {
      nativeBridge.callNative(JSON.stringify({
        method: method,
        data: data
      }), (response) => {
        resolve(JSON.parse(response));
      }, (error) => {
        reject(error);
      });
    });
  }
}

5. 性能优化和安全考虑

  1. 减少跨语言调用:批量处理数据,减少调用次数
  2. 数据验证:始终验证来自JS的数据
  3. 异常处理:妥善处理两边可能出现的错误
  4. 线程安全:CEF有严格的线程要求,UI操作必须在UI线程执行

CEF的JSBridge实现提供了灵活的方式在C++和JavaScript之间建立通信桥梁,开发者可以根据具体需求选择最适合的方式实现功能。

二 Android WebView 中 JSBridge 实现 Java 和 JavaScript 相互通信

Android WebView 提供了多种方式实现 Java 和 JavaScript 之间的双向通信,以下是主要的实现方法:

1. JavaScript 调用 Java 方法

1.1 使用 @JavascriptInterface 注解 (推荐方式)

Java 端代码

java

typescript 复制代码
public class WebAppInterface {
    private Context mContext;

    public WebAppInterface(Context context) {
        mContext = context;
    }

    // 暴露给JS调用的方法必须添加@JavascriptInterface注解
    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }

    @JavascriptInterface
    public String getDeviceInfo() {
        return Build.MODEL;
    }
}

// 在WebView设置中添加接口
WebView webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebAppInterface(this), "AndroidBridge");

JavaScript 端调用

javascript

arduino 复制代码
// 调用Java方法
AndroidBridge.showToast("Hello from JS!");

// 调用有返回值的方法
const deviceInfo = AndroidBridge.getDeviceInfo();
console.log(deviceInfo);

1.2 使用 shouldOverrideUrlLoading 拦截 URL 方案

Java 端代码

java

java 复制代码
webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith("jsbridge://")) {
            // 解析URL并处理JS调用
            Uri uri = Uri.parse(url);
            String action = uri.getHost();
            String params = uri.getQuery();
            
            if ("showToast".equals(action)) {
                Toast.makeText(MainActivity.this, params, Toast.LENGTH_SHORT).show();
            }
            return true;
        }
        return super.shouldOverrideUrlLoading(view, url);
    }
});

JavaScript 端调用

javascript

ini 复制代码
// 通过改变location.href触发
location.href = "jsbridge://showToast?message=Hello from JS!";

// 或者通过iframe方式(不会影响当前页面)
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = "jsbridge://getDeviceInfo";
document.body.appendChild(iframe);
setTimeout(() => document.body.removeChild(iframe), 100);

2. Java 调用 JavaScript 方法

2.1 使用 loadUrl 方法

java

arduino 复制代码
// 调用无返回值JS函数
webView.loadUrl("javascript:alert('Hello from Java!')");

// 调用有参数JS函数
String name = "World";
webView.loadUrl("javascript:greet('" + name + "')");

2.2 使用 evaluateJavascript (API 19+ 推荐方式)

java

typescript 复制代码
// 调用JS函数并获取返回值
webView.evaluateJavascript("sum(1, 2)", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String value) {
        // value是JS返回的JSON字符串
        Log.d("JSBridge", "Sum result: " + value);
    }
});

3. 完整双向通信实现方案

3.1 封装 JSBridge 类

Java 端

java

typescript 复制代码
public class JSBridge {
    private WebView mWebView;
    private Context mContext;

    public JSBridge(WebView webView, Context context) {
        mWebView = webView;
        mContext = context;
        setupBridge();
    }

    private void setupBridge() {
        // 添加JS接口
        mWebView.addJavascriptInterface(new JSBridgeInterface(), "NativeBridge");
    }

    // 调用JS方法
    public void callJS(String method, JSONObject params, final JSCallback callback) {
        String js = String.format("window.JSBridge.invoke('%s', %s, %s)", 
            method, 
            params.toString(),
            callback != null ? "true" : "false");
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            mWebView.evaluateJavascript(js, value -> {
                if (callback != null) {
                    try {
                        callback.onResult(new JSONObject(value));
                    } catch (JSONException e) {
                        callback.onError(e);
                    }
                }
            });
        } else {
            mWebView.loadUrl("javascript:" + js);
        }
    }

    // JS可调用的接口
    private class JSBridgeInterface {
        @JavascriptInterface
        public void callNative(String method, String params, String callbackId) {
            // 处理来自JS的调用
            if ("getUserInfo".equals(method)) {
                JSONObject result = new JSONObject();
                try {
                    result.put("name", "Android User");
                    result.put("age", 30);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                
                // 回调JS
                String js = String.format("window.JSBridge.callback('%s', %s)", 
                    callbackId, result.toString());
                mWebView.post(() -> mWebView.evaluateJavascript(js, null));
            }
        }
    }

    public interface JSCallback {
        void onResult(JSONObject result);
        void onError(Exception error);
    }
}

JavaScript 端

javascript

javascript 复制代码
// 初始化JSBridge
window.JSBridge = {
    callbacks: {},
    callbackId: 0,
    
    // 供Native调用JS
    invoke: function(method, params, needCallback) {
        if (window[method]) {
            const result = window[method](params);
            if (needCallback) {
                return JSON.stringify(result);
            }
        }
        return null;
    },
    
    // 调用Native方法
    callNative: function(method, params, callback) {
        const callbackId = this.callbackId++;
        this.callbacks[callbackId] = callback;
        
        // 调用Android接口
        if (window.NativeBridge) {
            NativeBridge.callNative(method, JSON.stringify(params), callbackId.toString());
        } else {
            console.error("NativeBridge not found!");
        }
    },
    
    // Native回调JS
    callback: function(callbackId, result) {
        const callback = this.callbacks[callbackId];
        if (callback) {
            callback(JSON.parse(result));
            delete this.callbacks[callbackId];
        }
    }
};

// 使用示例
function getUserInfo() {
    return { userId: 123 };
}

// 调用Native方法
JSBridge.callNative('getDeviceInfo', {}, function(result) {
    console.log('Device info:', result);
});

4. 安全注意事项

  1. 仅允许必要的JS接口:不要暴露过多功能给JS

  2. 参数验证:对所有来自JS的参数进行验证

  3. HTTPS安全:建议使用HTTPS防止中间人攻击

  4. WebView安全设置

    java

    scss 复制代码
    // 禁用文件访问
    webView.getSettings().setAllowFileAccess(false);
    webView.getSettings().setAllowFileAccessFromFileURLs(false);
    webView.getSettings().setAllowUniversalAccessFromFileURLs(false);
    
    // 禁用内容提供者访问
    webView.getSettings().setAllowContentAccess(false);

5. 性能优化建议

  1. 减少跨语言调用:批量处理数据
  2. 使用JSON格式:统一数据交换格式
  3. 异步通信:避免阻塞UI线程
  4. 缓存机制:对频繁调用的结果进行缓存

通过以上方式,可以在Android WebView中建立安全高效的Java和JavaScript通信桥梁。

PS:创作不易 学会了记得,点赞,评论,收藏,分享

相关推荐
JSON_L6 小时前
Vue rem回顾
前端·javascript·vue.js
GISer_Jing7 小时前
JavaScript 中Object、Array 和 String的常用方法
开发语言·javascript·ecmascript
Moment9 小时前
基于 Tiptap + Yjs + Hocuspocus 的富文本协同项目,期待你的参与 😍😍😍
前端·javascript·react.js
Krorainas10 小时前
HTML 页面禁止缩放功能
前端·javascript·html
whhhhhhhhhw10 小时前
Vue3.6 无虚拟DOM模式
前端·javascript·vue.js
仰望星空的凡人11 小时前
【JS逆向基础】数据库之mysql
javascript·数据库·python·mysql
清风细雨_林木木12 小时前
Vuex 的语法“...mapActions([‘login‘]) ”是用于在组件中映射 Vuex 的 actions 方法
前端·javascript·vue.js
会功夫的李白12 小时前
Uniapp之自定义图片预览
前端·javascript·uni-app·图片预览
ℳ๓. Sweet12 小时前
【STM32】关于STM32F407写Flash失败问题的解决办法
javascript·stm32·嵌入式硬件
拾光拾趣录12 小时前
script 标签上有那些属性,分别作用是啥?
前端·javascript