摘要
文章讲述混合应用开发中混合开发模式的意义,介绍 JSBridge 作为 H5 与 Native 双向通信机制的概念、作用、重要性、实现原理,还阐述了 Native 与 Web 双向调用的方式以及 H5 端的具体实现。
一、混合开发概述
混合开发是一种使用多种开发模型(原生 Native 和 Web H5)开发 App 的模式。原生开发效率低,重新打包依赖用户更新,但性能高功能覆盖率高;Web H5 发布更新方便,跨平台性好但性能低特性受限。随着手机硬件和系统对 Web 特性支持变好,混合开发的意义在于结合两者优点。
二、JSBridge 的概念和作用
(一)通信桥梁
JSBridge 充当 Web 和原生应用间的通信桥梁,可双向通信,互相调用和传递数据。
(二)原生功能调用
在 JavaScript 中能调用原生应用功能,触发原生操作如打开相机等。
(三)数据传递
JavaScript 和原生代码间可传递复杂数据结构,如对象、数组等。
(四)回调机制
支持回调机制,原生操作完成后可通知 JavaScript 并传递结果。
三、JSBridge 的重要性
(一)跨平台开发
允许一套代码运行在不同平台,用 Web 技术开发核心逻辑,必要时调用原生功能,提高开发效率。
(二)原生功能扩展
充分利用原生平台功能,如访问硬件设备、调用系统 API,丰富应用功能提升用户体验。
(三)灵活性和扩展性
灵活可扩展地实现 Web 和原生代码通信,开发人员可按需添加原生功能并在 JavaScript 中调用。
四、JSBridge 的实现原理
将 Web 和 Native 端通信类比为 Client/Server 模式,JSBridge 类似 HTTP 协议。Native 端把原生接口封装成 JavaScript 接口并注册到全局对象;Web 端把 JavaScript 接口封装成原生接口暴露给原生代码。
五、Native 与 Web 的双向调用
(一)Native -> Web
JavaScript 是解释性语言,Native 端调用 Web 端时,可将拼接的 JavaScript 代码字符串传入 JS 解析器(webView)执行。
1. Android
Android 提供 evaluateJavascript 来执行 JS 代码并获取返回值执行回调,如:
String jsCode = String.format("window.showWebDialog('%s')", text);
webView.evaluateJavascript(jsCode, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});
2. IOS
IOS 的 WKWebView 使用 evaluateJavaScript,如:
[webView evaluateJavaScript:@"执行的 JS 代码"
completionHandler:^(id _Nullable response, NSError * _Nullable error) {
//
}];
(二)Web -> Native
1. URL Schema
是类 URL 的请求格式,可自定义用于 JSBridge 通信。Native 可重写 WebView 方法拦截 Web 请求,符合自定义格式则解析调用原生方法,否则转发。如:
get existOrderRedirect() {
let url: string;
if (this.env.isHelloBikeApp) {
url = 'hellobike://hellobike.com/xxxxx_xxx?from_type=xxxx & selected_tab=xxxxx';
} else if (this.env.isSFCApp) {
url = 'hellohitch://hellohitch.com/xxx/xxxx?bottomTab=xxxx';
}
return url;
}
但这种方式基于 URL,有长度、直观性、数据格式限制,建立请求耗时。
2. 在 Webview 中注入 JS API
App 将 Native 接口注入到 JS 的 Context(window)对象中,Web 端可调用原生方法。
Android
4.2 前用 addJavascriptInterface 接口有漏洞,4.2 后用@JavascriptInterface 解决兼容性问题。
IOS
UIWebView 的 JavaSciptCore 支持 iOS 7.0 及以上,WKWebView 的 WKScriptMessageHandler 支持 iOS 8.0 及以上。例如:
// 注入全局 JS 对象
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
class NativeBridge {
private Context ctx;
NativeBridge(Context ctx) {
this.ctx = ctx;
}
// 绑定方法
@JavascriptInterface
public void showNativeDialog(String text) {
new AlertDialog.Builder(ctx).setMessage(text).create().show();
}
}
// 调用 nativeBridge 的方法
window.NativeBridge.showNativeDialog('hello');
六、H5 具体实现
将功能抽象为 AppBridge 类,封装交互和回调方法。
(一)调用原生方法
定义 callNative 方法在 JavaScript 中调用原生方法,根据平台调用相应原生方法。
callNative<P, R>(classMap: string, method: string, params: P): Promise<R> {
return new Promise<R>((resolve, reject) => {
const id = v4();
this.__callbacks[id] = { resolve, reject, method: `${classMap} - ${method}` };
const data = {
classMap,
method,
params: params === null? '' : JSON.stringify(params),
callbackId: id,
};
const dataStr = JSON.stringify(data);
if (this.env.isIOS && isFunction(window?.webkit?.messageHandlers?.callNative?.postMessage)) {
window.webkit.messageHandlers.callNative.postMessage(dataStr);
} else if (this.env.isAndroid && isFunction(window?.AppFunctions?.callNative)) {
window.AppFunctions.callNative(dataStr);
}
});
}
(二)回调处理
初始化桥接回调函数,重新定义 window.callBack 方法处理原生回调数据。
private initBridgeCallback() {
const oldCallback = window.callBack;
window.callBack = (data) => {
if (isFunction(oldCallback)) {
oldCallback(data);
}
console.info('native callback', data, data.callbackId);
const { callbackId } = data;
const callback = this.__callbacks[callbackId];
if (callback) {
if (data.code === 0) {
callback.resolve(data.data);
} else {
const error = new Error(data.msg) as Error & {response:unknown};
error.response = data;
callback.reject(error);
}
delete this.__callbacks[callbackId];
}
};
}
(三)使用示例
包括调用原生方法、打开 webview、获取驾驶证 OCR 信息等示例。
// 调用原生方法的封装函数
callNative<P, R>(classMap: string, method: string, params: P) {
const bridge = container.resolve<AppBridge>(AppBridge);
const func = bridge.callNative.bind(bridge);
return func<P, R>(classMap, method, params);
}
// 打开 webview
openWebView(url: string): Promise<void> {
return this.callNative<{url:string}, void>('xxxxx/hitch', 'openWebview', { url });
}
// 获取驾驶证 OCR 信息
getDriverLicenseOcrInfo(
params: HBNative.getDriverLicenseOcrInfo.Params,
): Promise<HBNative.getDriverLicenseOcrInfo.Result> {
return this.callNative<
HBNative.getDriverLicenseOcrInfo.Params,
HBNative.getDriverLicenseOcrInfo.Result>(
'xxxxx/hitch', 'getOcrInfo', params,
);
}