赞
踩
在混合应用开发中,一种常见且成熟的技术方案是将原生应用与 WebView 结合,使得复杂的业务逻辑可以通过网页技术实现。实现这种类型的混合应用时,就需要解决H5与Native之间的双向通信。JSBridge 是一种在混合应用中实现 Web 和原生代码之间通信的重要机制。
混合开发(Hybrid)是一种开发模式,指使用多种开发模型开发App,通常会涉及到两大类技术:
原生 Native、Web H5
混合开发的意义就在于吸取两者的优点,而且随着手机硬件的升级迭代、系统(Android 5.0+、ISO 9.0+)对于Web特性的较好支持,H5的劣势被逐渐缩小。
在Hybrid模式下,H5会需要使用Native的功能,比如打开二维码扫描、调用原生页面、获取用户信息等,同时Native也需要向Web端发送推送、更新状态等,而JavaScript是运行在单独的 JS Context 中(Webview容器)与原生有运行环境的隔离,所以需要有一种机制实现Native端和Web端的 双向通信 ,这就是JSBridge:以JavaScript引擎或Webview容器作为媒介,通过协定协议进行通信,实现Native端和Web端双向通信的一种机制。
通过JSBridge,Web端可以调用Native端的Java接口,同样Native端也可以通过JSBridge调用Web端的JavaScript接口,实现彼此的双向调用。
把 Web 端和 Native 端的通信比作 Client/Server 模式。JSBridge 充当了类似于 HTTP 协议的角色,实现了 Web 端和 Native 端之间的通信。
将 Native 端原生接口封装成 JavaScript 接口:在 Native 端将需要被调用的原生功能封装成 JavaScript 接口,让 JavaScript 代码可以调用。 JavaScript 接口会被注册到全局对象中,以供 JavaScript 代码调用。
将 Web 端 JavaScript 接口封装成原生接口: 这一步是在 Web 端将需要被调用的 JavaScript 功能封装成原生接口。这些原生接口会通过 WebView 的某些机制暴露给原生代码,以供原生代码调用。
Native端调用Web端,JavaScript作为解释性语言,最大的一个特性就是可以随时随地地通过解释器执行一段JS代码,所以可以将拼接的JavaScript代码字符串,传入JS解析器执行就可以,JS解析器在这里就是webView。
Android 提供了 evaluateJavascript 来执行JS代码,并且可以获取返回值执行回调:
- String jsCode = String.format("window.showWebDialog('%s')", text);
- webView.evaluateJavascript(jsCode, new ValueCallback<String>() {
- @Override
- public void onReceiveValue(String value) {
-
- }
- });
IOS的 WKWebView 使用 evaluateJavaScript:
- [webView evaluateJavaScript:@"执行的JS代码"
- completionHandler:^(id _Nullable response, NSError * _Nullable error) {
- //
- }];
Web调用Native端主要有两种方式
URL Schema是类URL的一种请求格式,格式如下:
- <protocol>://<host>/<path>?<qeury>#fragment
-
- // 我们可以自定义JSBridge通信的URL Schema,比如:
- hellobike://showToast?text=hello
Native加载WebView之后,Web发送的所有请求都会经过WebView组件,所以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的方式,长度受到限制而且不太直观,数据格式有限制,而且建立请求有时间耗时。
通过webView提供的接口,App将Native的相关接口注入到JS的Context(window)的对象中
Web端就可以直接在全局 window 下使用这个暴露的全局JS对象,进而调用原生端的方法。
Android注入方法:
IOS注入方法:
例如:
- // 注入全局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');
将功能抽象为一个 AppBridge 类,封装两个方法,处理交互和回调
具体步骤:
具体实现代码:
- // 定义一个名为 callNative 的方法,用于在 JavaScript 中调用原生方法
- callNative<P, R>(classMap: string, method: string, params: P): Promise<R> {
- return new Promise<R>((resolve, reject) => {
- // 生成一个唯一的回调 ID
- const id = v4();
- // 将当前的回调函数保存到 __callbacks 对象中,以 callbackId 作为键
- this.__callbacks[id] = { resolve, reject, method: `${classMap} - ${method}` };
- // 构造通信数据,包括原生类映射、要调用的方法、参数和 callbackId
- const data = {
- classMap,
- method,
- params: params === null ? '' : JSON.stringify(params),
- callbackId: id,
- };
- const dataStr = JSON.stringify(data);
- // 根据当前环境判断是 iOS 还是 Android,并调用相应平台的原生方法
- if (this.env.isIOS && isFunction(window?.webkit?.messageHandlers?.callNative?.postMessage)) {
- // 如果是 iOS 平台,则调用 iOS 的原生方法
- window.webkit.messageHandlers.callNative.postMessage(dataStr);
- } else if (this.env.isAndroid && isFunction(window?.AppFunctions?.callNative)) {
- // 如果是 Android 平台,则调用 Android 的原生方法
- window.AppFunctions.callNative(dataStr);
- }
- });
- }
- // 初始化桥接回调函数,该参数在 constructor 中调用
- private initBridgeCallback() {
- // 保存旧的回调函数到 oldCallback 变量中
- const oldCallback = window.callBack;
- // 重新定义 window.callBack 方法,用于处理原生应用的回调数据
- window.callBack = (data) => {
- // 如果存在旧的回调函数,则调用旧的回调函数
- if (isFunction(oldCallback)) {
- oldCallback(data);
- }
- // 获取原生应用的回调信息,包括数据和回调 ID
- console.info('native callback', data, data.callbackId);
- // 从回调数据中获取回调 ID
- const { callbackId } = data;
- // 根据回调 ID 查找对应的回调函数
- const callback = this.__callbacks[callbackId];
- // 如果找到了对应的回调函数
- if (callback) {
- // 如果回调数据中的 code 为 0,则表示执行成功,调用 resolve 方法处理成功的结果
- if (data.code === 0) {
- callback.resolve(data.data);
- } else {
- // 否则,表示执行失败,构造一个错误对象并调用 reject 方法处理错误信息
- const error = new Error(data.msg) as Error & {response:unknown};
- error.response = data;
- callback.reject(error);
- }
- // 删除已经处理过的回调函数
- delete this.__callbacks[callbackId];
- }
- };
- }
- // 调用原生方法的封装函数
- callNative<P, R>(classMap: string, method: string, params: P) {
- // 从容器中解析出 AppBridge 实例
- const bridge = container.resolve<AppBridge>(AppBridge);
- // 使用 bind 方法将 AppBridge 实例中的 callNative 方法绑定到 bridge 对象上,并保存到 func 变量中
- const func = bridge.callNative.bind(bridge);
- // 调用 func 方法,并传入 classMap、method 和 params 参数,实现调用原生方法的功能
- return func<P, R>(classMap, method, params);
- }
-
-
- // 打开 webview
- // 调用 callNative 方法,传入参数 url,classMap 为 'xxxxx/hitch',method 为 'openWebview'
- 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> {
- // 调用 callNative 方法,传入参数 params,classMap 为 'xxxxx/hitch',method 为 'getOcrInfo'
- // 返回一个 Promise 对象,该 Promise 对象用于处理异步结果
- return this.callNative<
- HBNative.getDriverLicenseOcrInfo.Params,
- HBNative.getDriverLicenseOcrInfo.Result>(
- 'xxxxx/hitch', 'getOcrInfo', params,
- );
- }
-
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。