实现WebView JSBridge
引言
JSBridge是Hybrid应用开发中的核心技术,它架起了JavaScript与Native之间的通信桥梁。虽然市面上有许多开源的JSBridge库,但深入理解其实现原理并自己动手实现一个,不仅能让我们更好地掌握跨端通信的本质,也能根据业务需求进行灵活定制。本文将带你从零开始,完整实现一个功能完善、性能优良的JSBridge框架,涵盖JavaScript端、iOS端和Android端的全部代码。
一、整体架构设计
1.1 架构概览
一个完整的JSBridge系统由三部分组成:
graph TB
subgraph JavaScript端
A[JSBridge.js]
A1[方法注册]
A2[消息队列]
A3[回调管理]
end
subgraph Native桥接层
B[URL拦截器]
C[消息解析器]
D[方法路由]
end
subgraph Native功能层
E[相机模块]
F[定位模块]
G[支付模块]
H[分享模块]
end
A --> A1
A --> A2
A --> A3
A1 --> B
A2 --> B
B --> C
C --> D
D --> E
D --> F
D --> G
D --> H
E -.回调.-> A3
F -.回调.-> A3
G -.回调.-> A3
H -.回调.-> A3
style A fill:#e3f2fd
style B fill:#fff3e0
style D fill:#f3e5f5
1.2 通信协议设计
协议格式定义:
javascript
// 标准协议格式
{
"protocol": "jsbridge", // 协议标识
"version": "1.0", // 版本号
"method": "methodName", // 调用的方法名
"params": { // 参数对象
"key1": "value1",
"key2": "value2"
},
"callbackId": "cb_1234567890", // 回调ID
"timestamp": 1640000000000 // 时间戳
}
URL Schema格式:
css
jsbridge://methodName?data=encodeURIComponent(JSON.stringify(protocolData))
1.3 调用流程
sequenceDiagram
participant JS as JavaScript
participant Queue as 消息队列
participant Bridge as JSBridge
participant Native as Native
participant Module as 功能模块
JS->>JS: 调用bridge.invoke()
JS->>Queue: 消息入队
Queue->>Bridge: 批量取出消息
Bridge->>Native: URL Schema触发
Native->>Native: 解析URL
Native->>Module: 调用原生方法
Module->>Module: 执行功能
Module-->>Native: 返回结果
Native-->>Bridge: evaluateJavaScript
Bridge-->>JS: 触发回调函数
JS->>JS: 执行业务逻辑
二、JavaScript端完整实现
2.1 核心类设计
JSBridge核心类的完整实现:
javascript
class JSBridge {
constructor(config = {}) {
// 配置项
this.config = {
scheme: config.scheme || 'jsbridge', // URL Schema
timeout: config.timeout || 10000, // 超时时间
debug: config.debug || false, // 调试模式
useQueue: config.useQueue !== false // 是否使用消息队列
};
// 回调函数存储
this.callbacks = new Map();
// 回调ID计数器
this.callbackId = 0;
// 消息队列
this.messageQueue = [];
// 队列处理定时器
this.queueTimer = null;
// Native注册的方法
this.nativeMethods = new Set();
// 初始化
this._init();
}
/**
* 初始化
*/
_init() {
// 挂载全局回调处理函数
window._JSBridgeCallback = this._handleCallback.bind(this);
// 挂载全局方法注册函数
window._JSBridgeRegisterMethod = this._registerNativeMethod.bind(this);
// 启动消息队列处理
if (this.config.useQueue) {
this._startQueueProcessor();
}
this._log('JSBridge initialized');
}
/**
* 调用Native方法
* @param {string} method - 方法名
* @param {object} params - 参数
* @param {function} callback - 回调函数
* @returns {Promise} Promise对象
*/
invoke(method, params = {}, callback = null) {
return new Promise((resolve, reject) => {
// 生成回调ID
const callbackId = this._generateCallbackId();
// 存储回调
this.callbacks.set(callbackId, {
resolve,
reject,
callback,
timestamp: Date.now()
});
// 设置超时
this._setCallbackTimeout(callbackId);
// 构造消息
const message = this._buildMessage(method, params, callbackId);
// 发送消息
this._sendMessage(message);
this._log('Invoke:', method, params);
});
}
/**
* 注册JS方法供Native调用
* @param {string} method - 方法名
* @param {function} handler - 处理函数
*/
registerHandler(method, handler) {
if (typeof handler !== 'function') {
throw new Error('Handler must be a function');
}
this[method] = handler;
this._log('Register handler:', method);
}
/**
* 批量注册方法
* @param {object} handlers - 方法对象
*/
registerHandlers(handlers) {
Object.keys(handlers).forEach(method => {
this.registerHandler(method, handlers[method]);
});
}
/**
* 生成回调ID
* @returns {string} 回调ID
*/
_generateCallbackId() {
return `cb_${Date.now()}_${++this.callbackId}`;
}
/**
* 构造消息对象
* @param {string} method - 方法名
* @param {object} params - 参数
* @param {string} callbackId - 回调ID
* @returns {object} 消息对象
*/
_buildMessage(method, params, callbackId) {
return {
protocol: 'jsbridge',
version: '1.0',
method: method,
params: params,
callbackId: callbackId,
timestamp: Date.now()
};
}
/**
* 发送消息
* @param {object} message - 消息对象
*/
_sendMessage(message) {
if (this.config.useQueue) {
// 加入队列
this.messageQueue.push(message);
} else {
// 直接发送
this._doSend(message);
}
}
/**
* 实际发送消息
* @param {object} message - 消息对象
*/
_doSend(message) {
const url = this._buildURL(message);
// 使用iframe触发
const iframe = document.createElement('iframe');
iframe.style.cssText = 'display:none;width:0;height:0;';
iframe.src = url;
document.documentElement.appendChild(iframe);
setTimeout(() => {
document.documentElement.removeChild(iframe);
}, 100);
}
/**
* 构造URL
* @param {object} message - 消息对象
* @returns {string} URL字符串
*/
_buildURL(message) {
const data = encodeURIComponent(JSON.stringify(message));
return `${this.config.scheme}://${message.method}?data=${data}`;
}
/**
* 启动队列处理器
*/
_startQueueProcessor() {
this.queueTimer = setInterval(() => {
if (this.messageQueue.length > 0) {
// 取出所有消息
const messages = this.messageQueue.splice(0);
// 批量发送
messages.forEach(message => {
this._doSend(message);
});
}
}, 50); // 每50ms处理一次队列
}
/**
* 停止队列处理器
*/
_stopQueueProcessor() {
if (this.queueTimer) {
clearInterval(this.queueTimer);
this.queueTimer = null;
}
}
/**
* 设置回调超时
* @param {string} callbackId - 回调ID
*/
_setCallbackTimeout(callbackId) {
setTimeout(() => {
const callbackInfo = this.callbacks.get(callbackId);
if (callbackInfo) {
this.callbacks.delete(callbackId);
const error = new Error(`Timeout: ${this.config.timeout}ms`);
callbackInfo.reject(error);
this._log('Callback timeout:', callbackId);
}
}, this.config.timeout);
}
/**
* 处理Native回调
* @param {string} callbackId - 回调ID
* @param {object} response - 响应数据
*/
_handleCallback(callbackId, response) {
const callbackInfo = this.callbacks.get(callbackId);
if (!callbackInfo) {
this._log('Callback not found:', callbackId);
return;
}
this.callbacks.delete(callbackId);
// 执行用户回调
if (callbackInfo.callback) {
callbackInfo.callback(response);
}
// 处理Promise
if (response.code === 0 || response.success) {
callbackInfo.resolve(response.data || response);
} else {
const error = new Error(response.message || 'Unknown error');
error.code = response.code;
callbackInfo.reject(error);
}
this._log('Callback executed:', callbackId, response);
}
/**
* 注册Native方法
* @param {string} method - 方法名
*/
_registerNativeMethod(method) {
this.nativeMethods.add(method);
this._log('Native method registered:', method);
}
/**
* 检查Native方法是否可用
* @param {string} method - 方法名
* @returns {boolean} 是否可用
*/
hasNativeMethod(method) {
return this.nativeMethods.has(method);
}
/**
* 调试日志
* @param {...any} args - 日志参数
*/
_log(...args) {
if (this.config.debug) {
console.log('[JSBridge]', ...args);
}
}
/**
* 销毁实例
*/
destroy() {
this._stopQueueProcessor();
this.callbacks.clear();
this.messageQueue = [];
this.nativeMethods.clear();
delete window._JSBridgeCallback;
delete window._JSBridgeRegisterMethod;
this._log('JSBridge destroyed');
}
}
// 导出单例
const bridge = new JSBridge({
debug: true,
useQueue: true
});
window.JSBridge = bridge;
export default bridge;
2.2 便捷方法封装
提供更友好的API接口:
javascript
// 常用方法的快捷封装
class JSBridgeHelper {
constructor(bridge) {
this.bridge = bridge;
}
/**
* 显示Toast提示
* @param {string} message - 提示消息
* @param {number} duration - 持续时间(ms)
*/
async showToast(message, duration = 2000) {
return this.bridge.invoke('showToast', { message, duration });
}
/**
* 显示加载框
* @param {string} message - 提示消息
*/
async showLoading(message = '加载中...') {
return this.bridge.invoke('showLoading', { message });
}
/**
* 隐藏加载框
*/
async hideLoading() {
return this.bridge.invoke('hideLoading');
}
/**
* 获取用户信息
*/
async getUserInfo() {
return this.bridge.invoke('getUserInfo');
}
/**
* 获取位置信息
* @param {string} type - 坐标类型 wgs84/gcj02
*/
async getLocation(type = 'wgs84') {
return this.bridge.invoke('getLocation', { type });
}
/**
* 选择图片
* @param {object} options - 配置选项
*/
async chooseImage(options = {}) {
const defaultOptions = {
count: 9,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera']
};
return this.bridge.invoke('chooseImage', {
...defaultOptions,
...options
});
}
/**
* 分享内容
* @param {object} data - 分享数据
*/
async share(data) {
return this.bridge.invoke('share', data);
}
/**
* 调起支付
* @param {object} payData - 支付数据
*/
async pay(payData) {
return this.bridge.invoke('pay', payData);
}
/**
* 设置导航栏标题
* @param {string} title - 标题文本
*/
async setNavigationBarTitle(title) {
return this.bridge.invoke('setNavigationBarTitle', { title });
}
/**
* 关闭当前WebView
*/
async closeWebView() {
return this.bridge.invoke('closeWebView');
}
/**
* 打开新的WebView
* @param {string} url - 页面URL
*/
async openWebView(url) {
return this.bridge.invoke('openWebView', { url });
}
}
// 导出便捷方法
const helper = new JSBridgeHelper(bridge);
export { helper };
2.3 使用示例
实际业务中的调用方式:
javascript
import bridge, { helper } from './JSBridge';
// 示例1: 基础调用
async function example1() {
try {
const result = await bridge.invoke('getUserInfo');
console.log('用户信息:', result);
} catch (error) {
console.error('获取失败:', error);
}
}
// 示例2: 使用便捷方法
async function example2() {
await helper.showLoading('获取位置中...');
try {
const location = await helper.getLocation();
await helper.hideLoading();
await helper.showToast(`位置: ${location.latitude}, ${location.longitude}`);
} catch (error) {
await helper.hideLoading();
await helper.showToast('获取位置失败');
}
}
// 示例3: 注册JS方法供Native调用
bridge.registerHandler('onUserLogin', (userData) => {
console.log('用户登录:', userData);
// 更新页面状态
updateUserUI(userData);
});
bridge.registerHandler('onNetworkChange', (networkInfo) => {
console.log('网络状态变化:', networkInfo);
// 处理网络变化
handleNetworkChange(networkInfo);
});
// 示例4: 批量注册方法
bridge.registerHandlers({
onPageResume: () => {
console.log('页面恢复');
refreshPageData();
},
onPagePause: () => {
console.log('页面暂停');
savePageState();
},
onAppBackground: () => {
console.log('应用进入后台');
}
});
// 示例5: 图片选择和上传
async function chooseAndUploadImage() {
try {
// 选择图片
const images = await helper.chooseImage({
count: 1,
sourceType: ['camera']
});
// 显示加载
await helper.showLoading('上传中...');
// 上传到服务器
const uploadResult = await uploadToServer(images[0]);
await helper.hideLoading();
await helper.showToast('上传成功');
return uploadResult;
} catch (error) {
await helper.hideLoading();
await helper.showToast('操作失败');
throw error;
}
}
三、iOS端实现(WKWebView)
3.1 JSBridge桥接类
Objective-C实现(用JavaScript语法描述):
javascript
// JSBridgeManager.h/m 伪代码
class JSBridgeManager {
constructor(webView) {
this.webView = webView;
this.handlers = new Map();
this._setupWebView();
}
/**
* 配置WebView
*/
_setupWebView() {
// 设置导航代理
this.webView.navigationDelegate = this;
// 注入初始化脚本
const initScript = this._getInitScript();
const userScript = new WKUserScript(
initScript,
WKUserScriptInjectionTime.AtDocumentStart,
false
);
this.webView.configuration.userContentController.addUserScript(userScript);
// 注册消息处理器
this.webView.configuration.userContentController.addScriptMessageHandler(
this,
'jsBridge'
);
}
/**
* 获取初始化脚本
*/
_getInitScript() {
return `
(function() {
// 注册所有可用的Native方法
const nativeMethods = ${JSON.stringify(Array.from(this.handlers.keys()))};
nativeMethods.forEach(method => {
if (window._JSBridgeRegisterMethod) {
window._JSBridgeRegisterMethod(method);
}
});
console.log('[JSBridge] Native methods registered:', nativeMethods);
})();
`;
}
/**
* WKNavigationDelegate - 拦截URL请求
*/
decidePolicyForNavigationAction(webView, navigationAction, decisionHandler) {
const url = navigationAction.request.URL.absoluteString;
// 检查是否是JSBridge协议
if (url.startsWith('jsbridge://')) {
// 拦截并处理
this._handleJSBridgeURL(url);
// 取消导航
decisionHandler(WKNavigationActionPolicy.Cancel);
} else {
// 允许导航
decisionHandler(WKNavigationActionPolicy.Allow);
}
}
/**
* 处理JSBridge URL
*/
_handleJSBridgeURL(urlString) {
try {
// 解析URL
const url = new URL(urlString);
const method = url.hostname;
const dataParam = url.searchParams.get('data');
if (!dataParam) {
console.error('[JSBridge] No data parameter');
return;
}
// 解析消息
const message = JSON.parse(decodeURIComponent(dataParam));
// 处理请求
this._handleMessage(message);
} catch (error) {
console.error('[JSBridge] Parse URL error:', error);
}
}
/**
* WKScriptMessageHandler - 接收JS消息
*/
userContentController(userContentController, didReceiveScriptMessage) {
const message = didReceiveScriptMessage.body;
this._handleMessage(message);
}
/**
* 处理消息
*/
async _handleMessage(message) {
const { method, params, callbackId } = message;
console.log('[JSBridge] Handle message:', method, params);
// 查找处理器
const handler = this.handlers.get(method);
if (!handler) {
this._callbackToJS(callbackId, {
code: -1,
message: `Method not found: ${method}`
});
return;
}
try {
// 执行处理器
const result = await handler(params);
// 回调成功
this._callbackToJS(callbackId, {
code: 0,
data: result
});
} catch (error) {
// 回调失败
this._callbackToJS(callbackId, {
code: error.code || -1,
message: error.message
});
}
}
/**
* 回调给JS
*/
_callbackToJS(callbackId, response) {
if (!callbackId) return;
const script = `
window._JSBridgeCallback(
'${callbackId}',
${JSON.stringify(response)}
);
`;
this.webView.evaluateJavaScript(script, (result, error) => {
if (error) {
console.error('[JSBridge] Callback error:', error);
}
});
}
/**
* 注册Native方法
*/
registerHandler(method, handler) {
this.handlers.set(method, handler);
console.log('[JSBridge] Register handler:', method);
}
/**
* JS调用Native方法
*/
callHandler(method, params) {
return new Promise((resolve, reject) => {
const callbackId = `native_cb_${Date.now()}`;
// 注册临时回调
window[callbackId] = (response) => {
delete window[callbackId];
if (response.code === 0) {
resolve(response.data);
} else {
reject(new Error(response.message));
}
};
// 调用JS方法
const script = `
(function() {
if (window.JSBridge && window.JSBridge['${method}']) {
const result = window.JSBridge['${method}'](${JSON.stringify(params)});
window['${callbackId}']({ code: 0, data: result });
} else {
window['${callbackId}']({
code: -1,
message: 'Method not found'
});
}
})();
`;
this.webView.evaluateJavaScript(script, null);
});
}
}
// 使用示例
const bridgeManager = new JSBridgeManager(webView);
// 注册方法
bridgeManager.registerHandler('getUserInfo', async (params) => {
// 获取用户信息
const userInfo = await UserManager.getCurrentUser();
return userInfo;
});
bridgeManager.registerHandler('getLocation', async (params) => {
// 获取位置
const location = await LocationManager.getCurrentLocation(params.type);
return {
latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude,
accuracy: location.horizontalAccuracy
};
});
bridgeManager.registerHandler('chooseImage', async (params) => {
// 打开相册
const images = await ImagePicker.pickImages(params);
return images;
});
3.2 具体功能模块实现
以定位功能为例:
javascript
// LocationHandler.swift 伪代码
class LocationHandler {
constructor() {
this.locationManager = new CLLocationManager();
this.locationManager.delegate = this;
this.resolver = null;
}
async getLocation(params) {
return new Promise((resolve, reject) => {
this.resolver = { resolve, reject };
// 检查权限
const authStatus = CLLocationManager.authorizationStatus();
if (authStatus === 'notDetermined') {
// 请求权限
this.locationManager.requestWhenInUseAuthorization();
} else if (authStatus === 'denied' || authStatus === 'restricted') {
reject(new Error('定位权限被拒绝'));
return;
}
// 配置定位管理器
this.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
this.locationManager.distanceFilter = 10;
// 开始定位
this.locationManager.startUpdatingLocation();
// 设置超时
setTimeout(() => {
this.locationManager.stopUpdatingLocation();
reject(new Error('获取位置超时'));
}, 10000);
});
}
// CLLocationManagerDelegate
didUpdateLocations(manager, locations) {
const location = locations[locations.length - 1];
// 停止定位
manager.stopUpdatingLocation();
// 转换坐标系(如果需要)
const coordinate = this._convertCoordinate(
location.coordinate,
this.params?.type
);
// 返回结果
if (this.resolver) {
this.resolver.resolve({
latitude: coordinate.latitude,
longitude: coordinate.longitude,
accuracy: location.horizontalAccuracy,
altitude: location.altitude,
speed: location.speed
});
this.resolver = null;
}
}
didFailWithError(manager, error) {
manager.stopUpdatingLocation();
if (this.resolver) {
this.resolver.reject(error);
this.resolver = null;
}
}
_convertCoordinate(coord, type) {
if (type === 'gcj02') {
// WGS84转GCJ02(国测局坐标)
return CoordinateConverter.wgs84ToGcj02(coord);
}
return coord;
}
}
四、Android端实现(WebView)
4.1 JSBridge桥接类
Kotlin实现(用JavaScript语法描述):
javascript
// JSBridgeManager.kt 伪代码
class JSBridgeManager {
constructor(webView, context) {
this.webView = webView;
this.context = context;
this.handlers = new Map();
this._setupWebView();
}
/**
* 配置WebView
*/
_setupWebView() {
const settings = this.webView.settings;
// 启用JavaScript
settings.javaScriptEnabled = true;
// 允许文件访问
settings.allowFileAccess = true;
// 设置缓存模式
settings.cacheMode = WebSettings.LOAD_DEFAULT;
// 支持缩放
settings.setSupportZoom(true);
settings.builtInZoomControls = true;
settings.displayZoomControls = false;
// DOM Storage
settings.domStorageEnabled = true;
// 设置WebViewClient
this.webView.webViewClient = new CustomWebViewClient(this);
// 设置WebChromeClient
this.webView.webChromeClient = new CustomWebChromeClient(this);
// 添加JavaScript接口(可选,用于更快速的通信)
this.webView.addJavascriptInterface(
new AndroidJSInterface(this),
'AndroidBridge'
);
}
/**
* 页面加载完成后注入初始化脚本
*/
onPageFinished(url) {
const initScript = `
(function() {
console.log('[JSBridge] Page loaded, initializing...');
// 通知Native已经ready
if (window.AndroidBridge) {
window.AndroidBridge.onJSBridgeReady();
}
})();
`;
this._evaluateJavaScript(initScript);
// 注册所有Native方法
this._registerNativeMethods();
}
/**
* 注册Native方法到JS
*/
_registerNativeMethods() {
const methods = Array.from(this.handlers.keys());
const script = `
(function() {
const methods = ${JSON.stringify(methods)};
methods.forEach(method => {
if (window._JSBridgeRegisterMethod) {
window._JSBridgeRegisterMethod(method);
}
});
})();
`;
this._evaluateJavaScript(script);
}
/**
* 自定义WebViewClient
*/
CustomWebViewClient = class {
constructor(manager) {
this.manager = manager;
}
shouldOverrideUrlLoading(view, request) {
const url = request.url.toString();
// 拦截JSBridge协议
if (url.startsWith('jsbridge://')) {
this.manager._handleJSBridgeURL(url);
return true; // 拦截
}
return false; // 正常加载
}
onPageFinished(view, url) {
this.manager.onPageFinished(url);
}
}
/**
* 处理JSBridge URL
*/
_handleJSBridgeURL(urlString) {
// 在主线程处理
this._runOnUiThread(() => {
try {
const uri = Uri.parse(urlString);
const method = uri.host;
const dataParam = uri.getQueryParameter('data');
if (!dataParam) {
console.error('[JSBridge] No data parameter');
return;
}
// 解析消息
const message = JSON.parse(decodeURIComponent(dataParam));
// 处理消息
this._handleMessage(message);
} catch (error) {
console.error('[JSBridge] Parse URL error:', error);
}
});
}
/**
* Android JS接口类
*/
AndroidJSInterface = class {
constructor(manager) {
this.manager = manager;
}
@JavascriptInterface
postMessage(messageJson) {
try {
const message = JSON.parse(messageJson);
this.manager._handleMessage(message);
} catch (error) {
console.error('[JSBridge] Parse message error:', error);
}
}
@JavascriptInterface
onJSBridgeReady() {
console.log('[JSBridge] JS Bridge is ready');
this.manager._registerNativeMethods();
}
}
/**
* 处理消息
*/
async _handleMessage(message) {
const { method, params, callbackId } = message;
console.log('[JSBridge] Handle message:', method, params);
// 查找处理器
const handler = this.handlers.get(method);
if (!handler) {
this._callbackToJS(callbackId, {
code: -1,
message: `Method not found: ${method}`
});
return;
}
try {
// 执行处理器
const result = await handler(params);
// 回调成功
this._callbackToJS(callbackId, {
code: 0,
data: result
});
} catch (error) {
// 回调失败
this._callbackToJS(callbackId, {
code: error.code || -1,
message: error.message
});
}
}
/**
* 回调给JS
*/
_callbackToJS(callbackId, response) {
if (!callbackId) return;
const script = `
window._JSBridgeCallback(
'${callbackId}',
${JSON.stringify(response)}
);
`;
this._evaluateJavaScript(script);
}
/**
* 执行JavaScript
*/
_evaluateJavaScript(script) {
this._runOnUiThread(() => {
if (Build.VERSION.SDK_INT >= 19) {
// Android 4.4+
this.webView.evaluateJavascript(script, null);
} else {
// Android 4.4以下
this.webView.loadUrl(`javascript:${script}`);
}
});
}
/**
* 在主线程运行
*/
_runOnUiThread(action) {
if (Looper.myLooper() === Looper.getMainLooper()) {
action();
} else {
new Handler(Looper.getMainLooper()).post(action);
}
}
/**
* 注册Native方法
*/
registerHandler(method, handler) {
this.handlers.set(method, handler);
console.log('[JSBridge] Register handler:', method);
}
/**
* Native调用JS方法
*/
callHandler(method, params) {
return new Promise((resolve, reject) => {
const callbackId = `native_cb_${Date.now()}`;
// 构造调用脚本
const script = `
(function() {
if (window.JSBridge && window.JSBridge['${method}']) {
try {
const result = window.JSBridge['${method}'](${JSON.stringify(params)});
window.AndroidBridge.handleNativeCallback(
'${callbackId}',
JSON.stringify({ code: 0, data: result })
);
} catch (error) {
window.AndroidBridge.handleNativeCallback(
'${callbackId}',
JSON.stringify({ code: -1, message: error.message })
);
}
} else {
window.AndroidBridge.handleNativeCallback(
'${callbackId}',
JSON.stringify({ code: -1, message: 'Method not found' })
);
}
})();
`;
// 存储回调
this.nativeCallbacks.set(callbackId, { resolve, reject });
// 执行脚本
this._evaluateJavaScript(script);
// 设置超时
setTimeout(() => {
if (this.nativeCallbacks.has(callbackId)) {
this.nativeCallbacks.delete(callbackId);
reject(new Error('Timeout'));
}
}, 10000);
});
}
}
// 使用示例
const bridgeManager = new JSBridgeManager(webView, context);
// 注册方法
bridgeManager.registerHandler('getUserInfo', async (params) => {
const userInfo = await UserManager.getCurrentUser();
return userInfo;
});
bridgeManager.registerHandler('getLocation', async (params) => {
const location = await LocationManager.getLocation(context, params);
return location;
});
bridgeManager.registerHandler('showToast', async (params) => {
Toast.makeText(context, params.message, Toast.LENGTH_SHORT).show();
return { success: true };
});
4.2 权限处理
Android权限请求示例:
javascript
// PermissionHandler.kt 伪代码
class PermissionHandler {
constructor(activity) {
this.activity = activity;
this.pendingRequest = null;
}
async requestPermission(permission) {
return new Promise((resolve, reject) => {
// 检查权限
if (ContextCompat.checkSelfPermission(this.activity, permission)
=== PackageManager.PERMISSION_GRANTED) {
resolve(true);
return;
}
// 请求权限
this.pendingRequest = { permission, resolve, reject };
ActivityCompat.requestPermissions(
this.activity,
[permission],
PERMISSION_REQUEST_CODE
);
});
}
onRequestPermissionsResult(requestCode, permissions, grantResults) {
if (requestCode !== PERMISSION_REQUEST_CODE) return;
if (!this.pendingRequest) return;
const granted = grantResults.length > 0
&& grantResults[0] === PackageManager.PERMISSION_GRANTED;
if (granted) {
this.pendingRequest.resolve(true);
} else {
this.pendingRequest.reject(new Error('Permission denied'));
}
this.pendingRequest = null;
}
}
// 定位功能使用权限
bridgeManager.registerHandler('getLocation', async (params) => {
try {
// 请求权限
await permissionHandler.requestPermission(
Manifest.permission.ACCESS_FINE_LOCATION
);
// 获取位置
const location = await LocationManager.getLocation(context);
return location;
} catch (error) {
throw new Error('需要定位权限');
}
});
五、通信协议优化
5.1 批量消息处理
优化消息队列机制:
javascript
class MessageQueue {
constructor(bridge) {
this.bridge = bridge;
this.queue = [];
this.processing = false;
this.batchSize = 10; // 批量处理数量
this.batchDelay = 50; // 批量延迟(ms)
this.timer = null;
}
/**
* 添加消息到队列
*/
enqueue(message) {
this.queue.push(message);
// 启动批处理
if (!this.timer) {
this.timer = setTimeout(() => {
this.process();
}, this.batchDelay);
}
}
/**
* 处理队列
*/
process() {
if (this.processing || this.queue.length === 0) {
return;
}
this.processing = true;
this.timer = null;
// 取出一批消息
const batch = this.queue.splice(0, this.batchSize);
// 批量发送
this._sendBatch(batch);
this.processing = false;
// 继续处理剩余消息
if (this.queue.length > 0) {
this.timer = setTimeout(() => {
this.process();
}, this.batchDelay);
}
}
/**
* 批量发送
*/
_sendBatch(messages) {
// 合并为一个请求
const batchData = {
protocol: 'jsbridge',
version: '1.0',
type: 'batch',
messages: messages,
timestamp: Date.now()
};
const url = `jsbridge://batch?data=${encodeURIComponent(
JSON.stringify(batchData)
)}`;
// 发送
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(() => {
document.body.removeChild(iframe);
}, 100);
}
/**
* 清空队列
*/
clear() {
this.queue = [];
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
}
5.2 数据压缩
大数据传输优化:
javascript
class DataCompressor {
/**
* 压缩数据
*/
static compress(data) {
const json = JSON.stringify(data);
// 如果数据小于1KB,不压缩
if (json.length < 1024) {
return {
compressed: false,
data: json
};
}
// 使用LZ压缩算法
const compressed = LZString.compressToBase64(json);
return {
compressed: true,
data: compressed,
originalSize: json.length,
compressedSize: compressed.length
};
}
/**
* 解压数据
*/
static decompress(compressedData) {
if (!compressedData.compressed) {
return JSON.parse(compressedData.data);
}
const decompressed = LZString.decompressFromBase64(compressedData.data);
return JSON.parse(decompressed);
}
}
// 在JSBridge中使用
class JSBridgeWithCompression extends JSBridge {
_buildMessage(method, params, callbackId) {
const message = super._buildMessage(method, params, callbackId);
// 压缩参数
if (params && Object.keys(params).length > 0) {
message.params = DataCompressor.compress(params);
}
return message;
}
_handleCallback(callbackId, response) {
// 解压响应数据
if (response.data && response.data.compressed) {
response.data = DataCompressor.decompress(response.data);
}
super._handleCallback(callbackId, response);
}
}
六、错误处理与调试
6.1 错误处理机制
javascript
class ErrorHandler {
constructor(bridge) {
this.bridge = bridge;
this.errorListeners = [];
}
/**
* 添加错误监听器
*/
onError(listener) {
this.errorListeners.push(listener);
}
/**
* 触发错误
*/
triggerError(error, context) {
const errorInfo = {
message: error.message,
code: error.code,
stack: error.stack,
context: context,
timestamp: Date.now()
};
// 通知所有监听器
this.errorListeners.forEach(listener => {
try {
listener(errorInfo);
} catch (e) {
console.error('[ErrorHandler] Listener error:', e);
}
});
// 上报错误
this._reportError(errorInfo);
}
/**
* 上报错误
*/
_reportError(errorInfo) {
// 上报到监控系统
if (this.bridge.hasNativeMethod('reportError')) {
this.bridge.invoke('reportError', errorInfo).catch(() => {
// 上报失败,使用本地存储
this._saveToLocal(errorInfo);
});
}
}
/**
* 保存到本地
*/
_saveToLocal(errorInfo) {
try {
const errors = JSON.parse(localStorage.getItem('jsbridge_errors') || '[]');
errors.push(errorInfo);
// 最多保存100条
if (errors.length > 100) {
errors.splice(0, errors.length - 100);
}
localStorage.setItem('jsbridge_errors', JSON.stringify(errors));
} catch (e) {
console.error('[ErrorHandler] Save to local error:', e);
}
}
}
// 使用错误处理
const errorHandler = new ErrorHandler(bridge);
errorHandler.onError((error) => {
console.error('[JSBridge Error]', error);
// 显示用户友好的提示
if (error.code === 'PERMISSION_DENIED') {
alert('需要相应权限才能使用此功能');
} else if (error.code === 'TIMEOUT') {
alert('操作超时,请重试');
} else {
alert('操作失败,请稍后重试');
}
});
6.2 调试工具
开发环境下的调试辅助:
javascript
class JSBridgeDebugger {
constructor(bridge) {
this.bridge = bridge;
this.enabled = false;
this.logs = [];
}
/**
* 启用调试模式
*/
enable() {
this.enabled = true;
this._injectDebugPanel();
this._interceptMethods();
}
/**
* 注入调试面板
*/
_injectDebugPanel() {
const panel = document.createElement('div');
panel.id = 'jsbridge-debug-panel';
panel.innerHTML = `
<div style="position:fixed;bottom:0;left:0;right:0;
background:#000;color:#0f0;padding:10px;
max-height:200px;overflow-y:auto;
font-size:12px;z-index:99999;">
<div id="jsbridge-logs"></div>
<button onclick="document.getElementById('jsbridge-debug-panel').remove()">
关闭调试
</button>
</div>
`;
document.body.appendChild(panel);
}
/**
* 拦截方法调用
*/
_interceptMethods() {
const originalInvoke = this.bridge.invoke.bind(this.bridge);
this.bridge.invoke = (method, params, callback) => {
const startTime = Date.now();
this.log('→ CALL', method, params);
return originalInvoke(method, params, callback)
.then(result => {
const duration = Date.now() - startTime;
this.log('← SUCCESS', method, result, `${duration}ms`);
return result;
})
.catch(error => {
const duration = Date.now() - startTime;
this.log('← ERROR', method, error.message, `${duration}ms`);
throw error;
});
};
}
/**
* 记录日志
*/
log(...args) {
const logEntry = {
timestamp: new Date().toLocaleTimeString(),
args: args
};
this.logs.push(logEntry);
// 显示在面板上
const logsDiv = document.getElementById('jsbridge-logs');
if (logsDiv) {
const logLine = document.createElement('div');
logLine.textContent = `[${logEntry.timestamp}] ${args.map(a =>
typeof a === 'object' ? JSON.stringify(a) : a
).join(' ')}`;
logsDiv.appendChild(logLine);
logsDiv.scrollTop = logsDiv.scrollHeight;
}
console.log('[JSBridge Debug]', ...args);
}
/**
* 导出日志
*/
exportLogs() {
const blob = new Blob([JSON.stringify(this.logs, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `jsbridge-logs-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
}
// 在开发环境启用调试
if (process.env.NODE_ENV === 'development') {
const debugger = new JSBridgeDebugger(bridge);
debugger.enable();
// 暴露到全局,方便控制台调用
window.JSBridgeDebugger = debugger;
}
七、完整使用示例
7.1 实战场景:用户登录流程
javascript
/**
* 用户登录完整流程
*/
class LoginManager {
constructor(bridge) {
this.bridge = bridge;
}
/**
* 执行登录
*/
async login(username, password) {
try {
// 1. 显示加载
await this.bridge.invoke('showLoading', {
message: '登录中...'
});
// 2. 调用登录接口
const loginResult = await this._callLoginAPI(username, password);
// 3. 保存用户信息到Native
await this.bridge.invoke('saveUserInfo', {
userInfo: loginResult.userInfo,
token: loginResult.token
});
// 4. 设置导航栏
await this.bridge.invoke('setNavigationBarTitle', {
title: `欢迎, ${loginResult.userInfo.nickname}`
});
// 5. 隐藏加载
await this.bridge.invoke('hideLoading');
// 6. 显示成功提示
await this.bridge.invoke('showToast', {
message: '登录成功',
duration: 2000
});
// 7. 跳转到首页
await this.bridge.invoke('navigateTo', {
url: '/pages/home/index'
});
return loginResult;
} catch (error) {
// 隐藏加载
await this.bridge.invoke('hideLoading');
// 显示错误提示
await this.bridge.invoke('showToast', {
message: error.message || '登录失败',
duration: 2000
});
throw error;
}
}
/**
* 调用登录API
*/
async _callLoginAPI(username, password) {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
if (!response.ok) {
throw new Error('登录失败');
}
return response.json();
}
/**
* 退出登录
*/
async logout() {
try {
// 1. 确认退出
const confirmed = await this.bridge.invoke('showModal', {
title: '提示',
content: '确定要退出登录吗?',
showCancel: true
});
if (!confirmed.confirm) {
return;
}
// 2. 清除用户信息
await this.bridge.invoke('clearUserInfo');
// 3. 跳转到登录页
await this.bridge.invoke('navigateTo', {
url: '/pages/login/index'
});
} catch (error) {
console.error('退出登录失败:', error);
}
}
}
// 使用
const loginManager = new LoginManager(bridge);
// 绑定登录按钮
document.getElementById('loginBtn').addEventListener('click', async () => {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
try {
await loginManager.login(username, password);
} catch (error) {
console.error('登录失败:', error);
}
});
7.2 实战场景:图片上传
javascript
/**
* 图片上传管理器
*/
class ImageUploadManager {
constructor(bridge) {
this.bridge = bridge;
}
/**
* 选择并上传图片
*/
async chooseAndUpload(options = {}) {
try {
// 1. 选择图片
const images = await this.bridge.invoke('chooseImage', {
count: options.count || 1,
sizeType: options.sizeType || ['compressed'],
sourceType: options.sourceType || ['album', 'camera']
});
if (!images || images.length === 0) {
return [];
}
// 2. 显示上传进度
await this.bridge.invoke('showLoading', {
message: '上传中 0%'
});
// 3. 逐个上传
const uploadedUrls = [];
for (let i = 0; i < images.length; i++) {
const localPath = images[i];
// 更新进度
const progress = Math.floor(((i + 1) / images.length) * 100);
await this.bridge.invoke('updateLoading', {
message: `上传中 ${progress}%`
});
// 上传单张图片
const url = await this._uploadSingleImage(localPath);
uploadedUrls.push(url);
}
// 4. 隐藏加载
await this.bridge.invoke('hideLoading');
// 5. 显示成功提示
await this.bridge.invoke('showToast', {
message: '上传成功',
duration: 1500
});
return uploadedUrls;
} catch (error) {
await this.bridge.invoke('hideLoading');
await this.bridge.invoke('showToast', {
message: '上传失败',
duration: 2000
});
throw error;
}
}
/**
* 上传单张图片
*/
async _uploadSingleImage(localPath) {
// 方式1: 通过Native上传(推荐)
if (this.bridge.hasNativeMethod('uploadImage')) {
const result = await this.bridge.invoke('uploadImage', {
filePath: localPath,
name: 'file',
url: 'https://api.example.com/upload'
});
return result.url;
}
// 方式2: 通过H5上传
const base64 = await this.bridge.invoke('getImageBase64', {
filePath: localPath
});
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
image: base64
})
});
const result = await response.json();
return result.url;
}
}
// 使用
const uploadManager = new ImageUploadManager(bridge);
document.getElementById('uploadBtn').addEventListener('click', async () => {
try {
const urls = await uploadManager.chooseAndUpload({
count: 3
});
console.log('上传成功,图片URL:', urls);
// 显示图片
displayImages(urls);
} catch (error) {
console.error('上传失败:', error);
}
});
八、总结
通过本文的详细讲解,我们完整实现了一个功能完善的JSBridge框架,涵盖了以下核心内容:
核心要点
- 架构设计: 清晰的分层架构,职责明确
- JavaScript端: 完整的类封装、消息队列、回调管理
- iOS端实现: WKWebView的URL拦截和消息处理
- Android端实现: WebView的多种通信方式
- 协议优化: 批量处理、数据压缩
- 错误处理: 完善的错误捕获和上报机制
- 调试工具: 开发环境下的调试辅助
最佳实践
- 使用消息队列避免频繁通信
- 合理设置超时时间
- 完善的错误处理机制
- 详细的日志记录
- 跨平台API统一
- 安全的权限校验
进阶方向
- 性能监控: 添加性能埋点,监控调用耗时
- 离线缓存: 支持离线调用和数据缓存
- 插件化: 支持动态注册和卸载功能模块
- TypeScript: 使用TypeScript增强类型安全
- 单元测试: 编写完整的测试用例
掌握JSBridge的实现原理和最佳实践,不仅能帮助我们更好地进行Hybrid开发,也为深入理解跨端通信机制打下坚实基础。