重点介绍了 做面混合应用和移动端混合应用 通过 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 扩展
- 创建处理类 :继承
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;
}
};
- 绑定到 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
- 从 JavaScript 发送消息到 C++
javascript
javascript
// JavaScript 发送消息
window.cefQuery({
request: 'my_request',
onSuccess: function(response) {},
onFailure: function(error_code, error_message) {}
});
- 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. 双向通信的最佳实践
推荐架构
- 在 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);
}
- 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. 性能优化和安全考虑
- 减少跨语言调用:批量处理数据,减少调用次数
- 数据验证:始终验证来自JS的数据
- 异常处理:妥善处理两边可能出现的错误
- 线程安全: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. 安全注意事项
-
仅允许必要的JS接口:不要暴露过多功能给JS
-
参数验证:对所有来自JS的参数进行验证
-
HTTPS安全:建议使用HTTPS防止中间人攻击
-
WebView安全设置:
java
scss// 禁用文件访问 webView.getSettings().setAllowFileAccess(false); webView.getSettings().setAllowFileAccessFromFileURLs(false); webView.getSettings().setAllowUniversalAccessFromFileURLs(false); // 禁用内容提供者访问 webView.getSettings().setAllowContentAccess(false);
5. 性能优化建议
- 减少跨语言调用:批量处理数据
- 使用JSON格式:统一数据交换格式
- 异步通信:避免阻塞UI线程
- 缓存机制:对频繁调用的结果进行缓存
通过以上方式,可以在Android WebView中建立安全高效的Java和JavaScript通信桥梁。
PS:创作不易 学会了记得,点赞,评论,收藏,分享