HarmonyOS鸿蒙应用开发——探索原生与H5通信框架DSBridge-HarmonyOS的奥秘 ...

打印 上一主题 下一主题

主题 847|帖子 847|积分 2541

HarmonyOS版的DSBridge是一个桥梁库,它允许鸿蒙原生环境与JavaScript之间进行交互,彼此能够调用对方的功能。该库目前兼容Android和iOS上第三方DSBridge库的核心功能,基本保持了原有的使用方式。
主要特性包罗


  • 已适配鸿蒙NEXT版本;
  • 支持在原生同步方法中执行串行异步并发使命,并同步期待异步结果,这是根据鸿蒙体系特点而设计的功能;
  • 同时兼容DSBridge 2.0与3.0版本的JS脚本;
  • 支持以类的情势集中统一管理API,同时也支持原生自定义页面组件的直接注册和使用;
  • 支持同步和异步调用方式;
  • 支持进度回调/回传功能,即一次调用可以多次返回结果;
  • 提供API存在性检测功能;
  • 支持监听和拦截JavaScript关闭页面的操作;
  • 支持定名空间API。
源码仓库
   

  • HarmonyOS版:DSBridge-HarmonyOS
  • Android版:DSBridge-Android(建议使用维护中的版本:github.com/751496032/D…)
  • iOS版:DSBridge-IOS
  由于原DSBridge库的作者已停止维护,对于Android端,我们推荐使用由社区维护的活泼版本,以确保得到最新的功能和修复。

安装指南

安装库
使用以下命令安装@hzw/ohos-dsbridge库:
  1. ohpm install @hzw/ohos-dsbridge
复制代码
或者安装本地HAR包
如果你有一个本地的HAR包,可以使用以下命令进行安装:
  1. ohpm install ../libs/library.har
复制代码
基本用法

在ArkTS原生侧,你可以通过以下两种方式之一来实现和管理API接口:
1.新建一个类来集中统一管理API
你可以创建一个名为JsBridge的类,并使用@JavaScriptInterface()装饰器来标注其方法,以便在JavaScript中调用。这个装饰器是为了保持与Android的一致性而自定义的。
  1. export class JsBridge {  
  2.   private cHandler: CompleteHandler = null;  
  3.   /**  
  4.    * 同步方法  
  5.    * @param p 输入参数  
  6.    * @returns 返回字符串  
  7.    */  
  8.   @JavaScriptInterface(false)  
  9.   testSync(p: string): string {  
  10.     LogUtils.d("testSync: " + JSON.stringify(p));  
  11.     return "hello native";  
  12.   }  
  13.   /**  
  14.    * 异步方法  
  15.    * @param p 输入参数  
  16.    * @param handler 回调处理函数  
  17.    */  
  18.   @JavaScriptInterface()  
  19.   testAsync(p: string, handler: CompleteHandler) {  
  20.     LogUtils.d("testAsync: " + JSON.stringify(p));  
  21.     this.cHandler = handler;  
  22.   }  
  23. }
复制代码
2.在自定义页面组件中直接注册使用
如果你不盼望使用类来管理API接口,你可以在自定义页面组件中直接注册并使用API接口。
  1. @Component  
  2. @Entry  
  3. struct UseInComponentsPage {  
  4.   aboutToAppear() {  
  5.     // 在一个组件内只能存在一个无命名空间的对象  
  6.     this.controller.addJavascriptObject(this);  
  7.   }  
  8.   /**  
  9.    * 组件中的同步方法  
  10.    * @param args 输入参数  
  11.    * @returns 返回字符串  
  12.    */  
  13.   @JavaScriptInterface(false)  
  14.   testComponentSync(args: string): string {  
  15.     return `组件中的同步方法: ${args}`;  
  16.   }  
  17.   /**  
  18.    * 组件中的异步方法  
  19.    * @param args 输入参数  
  20.    * @param handler 回调处理函数  
  21.    */  
  22.   @JavaScriptInterface()  
  23.   testComponentAsync(args: string, handler: CompleteHandler) {  
  24.     handler.complete(`组件中的异步方法: ${args}`);  
  25.   }  
  26. }
复制代码
注意


  • API的同步方法不支持使用async/await声明。如果必要在同步方法内执行异步使命,可以使用taskWait()函数来完成。
  • 异步方法的形参CompleteHandler可用于结果的异步回调。
Web组件中的JS注入与交互

在原生Web组件初始化时,你可以通过WebViewControllerProxy类来获取WebViewController实例,以实现JS注入,并将其关联到Web组件中。接着,将API管理类(如JsBridge)关联到WebViewControllerProxy中,以便在Web页面中调用原生方法。
  1. private controller: WebViewControllerProxy = WebViewControllerProxy.createController();  
  2.   
  3. aboutToAppear() {  
  4.   // 将JsBridge实例添加到WebView中,以便在JavaScript中访问  
  5.   this.controller.addJavascriptObject(new JsBridge());  
  6. }  
  7.   
  8. // 配置Web组件,并关联controller  
  9. Web({   
  10.   src: this.localPath,   
  11.   controller: this.controller.getWebViewController()   
  12. })  
  13. .javaScriptAccess(true) // 允许JavaScript执行  
  14. .javaScriptProxy(this.controller.getJavaScriptProxy()) // 设置JavaScript代理  
  15. .onAlert((event) => {  
  16.   // 处理JavaScript中的alert事件  
  17.   // AlertDialog.show({ message: event.message });  
  18.   return false;  
  19. });
复制代码
调用JavaScript函数

通过WebViewControllerProxy,你可以方便地调用JavaScript函数。callJs()方法提供了三个参数:第一个是JavaScript中注册的函数名称,第二个是通报给JavaScript函数的参数数组,第三个是监听JavaScript函数返回结果的回调函数。
  1. Button("调用js函数-同步")  
  2.   .onClick(() => {  
  3.     this.controller.callJs("showAlert", [1, 2, '666'], (v) => {  
  4.       this.msg = v + "";  
  5.     });  
  6.   });  
  7.   
  8. Button("调用js函数-异步")  
  9.   .onClick(() => {  
  10.     this.controller.callJs("showAlertAsync", [1, 2, '666'], (v) => {  
  11.       this.msg = v + "";  
  12.     });  
  13.   });
复制代码
兼容DSBridge2.0

如果你的前端使用的是DSBridge2.0的JS脚本,你可以通过supportDS2()方法来兼容。
  1. aboutToAppear() {  
  2.   // 如果是使用DSBridge2.0,调用supportDS2方法  
  3.   this.controller.supportDS2(true);  
  4.   
  5.   // 注册JavaScript对象,两种方式任选其一  
  6.   this.controller.addJavascriptObject(this);  
  7.   // 或者  
  8.   // this.controller.addJavascriptObject(new JsBridge2());  
  9.   
  10.   // 开启调试模式  
  11.   webview.WebviewController.setWebDebuggingAccess(true);  
  12. }
复制代码
这样,你就可以在原生Web组件中方便地实现与JavaScript的交互,并兼容不同的JS脚本版本。
JavaScript侧dsBridge初始化与交互

1.初始化dsBridge
你可以通过npm安装或CDN引入的方式来初始化dsBridge。如果项目没有历史包袱,建议直接使用m-dsbridge包。
  1. npm i m-dsbridge
复制代码
或者,通过CDN引入:
  1. <script src="https://cdn.jsdelivr.net/npm/m-dsbridge/dsBridge.js"></script>
复制代码
也支持直接使用原Android或iOS的DSBridge库的JS脚本:
  1. <script src="https://cdn.jsdelivr.net/npm/dsbridge/dist/dsbridge.js"></script>
复制代码
2.注册JavaScript函数供原生调用
通过dsBridge对象,你可以注册JavaScript函数,以便原生代码能够调用它们。
  1. // 注册同步函数  
  2. dsBridge.register('showAlert', function (a, b, c) {  
  3.   alert("原生调用JS showAlert函数 " + a + " " + b + " " + c);  
  4.   return true;  
  5. });  
  6. // 注册异步函数  
  7. dsBridge.registerAsyn('showAlertAsync', function (a, b, c, callback) {  
  8.   let counter = 0;  
  9.   let id = setInterval(() => {  
  10.     if (counter < 5) {  
  11.       callback(counter, false);  
  12.       alert("原生调用JS showAlertAsync函数 " + a + " " + b + " " + c + " " + counter);  
  13.       counter++;  
  14.     } else {  
  15.       callback(counter, true);  
  16.       alert("原生调用JS showAlertAsync函数 " + a + " " + b + " " + c + " " + counter);  
  17.       clearInterval(id);  
  18.     }  
  19.   }, 1000);  
  20. });
复制代码
在异步函数中,callback函数的末了一个参数如果返回true,则表示完成整个链接的调用;如果返回false,则可以一直回调给原生,实现JavaScript端的一次调用、多次返回。
3.调用原生API
通过dsBridge对象,你也可以调用原生API。第一个参数是原生方法名称,第二个参数是原生方法吸收的参数。对于异步方法,还有第三个参数是回调函数,用于吸收异步回调结果。
  1. // 同步调用  
  2. let msg = dsBridge.call('testSync', JSON.stringify({data: 100}));  
  3. // 异步调用  
  4. dsBridge.call('testAsync', JSON.stringify({data: 200}), (msg) => {  
  5.   updateMsg(msg);  
  6. });
复制代码
这样,你就可以在JavaScript侧方便地初始化dsBridge,注册函数供原生调用,以及调用原生API了。
进度回调与页面关闭监听

进度回调(一次调用,多次返回)

在原生端,也支持JavaScript端的一次调用、多次回调的模式,这在某些应用场景下非常有效,比如将原生的下载进度及时同步到JavaScript中。这可以通过CompleteHandler#setProgressData()方法来实现。
原生端示例(假设使用TypeScript和Android环境):
  1. @JavaScriptInterface()  
  2. testAsync(p: string, handler: CompleteHandler) {  
  3.   LogUtils.d("testAsync: " + JSON.stringify(p));  
  4.   this.cHandler = handler;  
  5.   let counter = 0;  
  6.   setInterval(() => {  
  7.     if (counter < 5) {  
  8.       counter++;  
  9.       handler.setProgressData("异步返回的数据--" + counter);  
  10.     } else {  
  11.       this.cHandler.complete("异步返回的数据--结束");  
  12.       // 注意:通常complete只调用一次,表示异步操作完成。如果需要发送多个结束信号,请确保逻辑的正确性。  
  13.       // this.cHandler.complete("异步返回的数据--结束2"); // 这行代码可能需要根据实际情况调整或删除。  
  14.     }  
  15.   }, 1000);  
  16. }
复制代码
JavaScript端调用:
  1. dsBridge.call('testAsync', JSON.stringify({data: 200}), (msg) => {  
  2.   updateMsg(msg);  
  3. });
复制代码
监听或拦截JavaScript关闭页面

当JavaScript调用close()函数尝试关闭当前页面时,原生代码可以设置监听器来观察并决定是否拦截这一行为。
原生端示例(假设使用TypeScript和某原生框架):
  1. aboutToAppear() {  
  2.   this.controller.setClosePageListener(() => {  
  3.     return false; // 返回false会拦截关闭页面的事件,返回true则允许页面关闭。  
  4.   });  
  5. }
复制代码
在回调函数中,如果返回false,则会拦截掉关闭页面的变乱;如果返回true,则允许页面正常关闭。这提供了一种机制,让原生代码能够控制页面的关闭行为。

销毁结束使命与定名空间管理

销毁结束使命

当异步使命(如setProgressData)仍在执行中,如果此时关闭页面,可能会导致应用闪退。为了避免这种情况,建议在组件的生命周期函数aboutToDisappear()中结束使命。
原生端示例(假设使用TypeScript和某原生框架):
  1. aboutToDisappear() {  
  2.   this.jsBridge.destroy(); // 销毁并结束所有正在执行的任务  
  3. }
复制代码
定名空间管理

定名空间可以资助你更好地管理API,尤其在API数量较多时。它允许你将API分类管理,不同级之间只需用.分隔即可。定名空间支持同步与异步方式使用。
原生端设置定名空间
原生端使用WebViewControllerProxy#addJavascriptObject方法指定一个定名空间名称:
  1. this.controller.addJavascriptObject(new JsBridgeNamespace(), "namespace");
复制代码
JavaScript端使用定名空间
在JavaScript中,使用定名空间名称加上对应的原生函数名来调用原生功能。
  1. const callNative6 = () => {  
  2.   let msg = dsBridge.call('namespace.testSync', { msg: '来自js命名空间的数据' });  
  3.   updateMsg(msg);  
  4. };  
  5.   
  6. const callNative7 = () => {  
  7.   dsBridge.call('namespace.testAsync', 'test', (msg) => {  
  8.     updateMsg(msg);  
  9.   });  
  10. };
复制代码
JavaScript端注册定名空间
你也可以在JavaScript端使用dsBridge对象注册js函数的定名空间。
  1. // 注册同步命名空间  
  2. dsBridge.register('sync', {  
  3.   test: function (a, b) {  
  4.     return "namespace:  " + (a + b);  
  5.   }  
  6. });  
  7.   
  8. // 注册异步命名空间  
  9. dsBridge.registerAsync("async", {  
  10.   test: function (a, b, callback) {  
  11.     callback("namespace:  " + (a + b));  
  12.   }  
  13. });
复制代码
第一个参数是定名空间的名称,比如sync或async,第二个参数是API业务对象实例,支持字面量对象和Class类实例。
原生端调用JavaScript定名空间中的函数
  1. this.controller.callJs("sync.test", [1, 2], (value: string) => {  
  2.   this.msg = value;  
  3. });  
  4.   
  5. this.controller.callJs("async.test", [3, 2], (value: string) => {  
  6.   this.msg = value;  
  7. });
复制代码
通过定名空间,你可以更加有序地管理你的API,使得代码更加清楚和易于维护。
在原生同步方法中执行串行异步并发使命

在鸿蒙体系中,原生同步方法通常不支持直接使用async/await来处理异步使命,这主要受到鸿蒙Web脚本注入机制的限制,同时也为了兼容Android/iOS项目。为了满足在同步方法中执行异步使命并立刻返回结果给H5的需求,我们设计了一个taskWait()函数。
taskWait()函数允许在同步方法中执行串行异步并发使命,主线程会同步期待异步结果。如果使命在3秒内未完成,函数会自动结束期待并返回结果,但这种情况下可能会存在数据丢失的风险。
以下是一个使用taskWait()函数在同步方法中执行异步使命的示例:
  1. /**  
  2. * 同步方法中执行异步并发任务  
  3. * @param args 传入参数  
  4. * @returns 返回计算结果  
  5. */  
  6. @JavaScriptInterface(false)  
  7. testTaskWait(args: string): number {  
  8.   let p = new Param(100, 200);  
  9.   let p1 = new Param(100, 300);  
  10.   
  11.   // 执行异步任务并等待结果  
  12.   taskWait(p);  
  13.   p1.tag = "Param";  
  14.   taskWait(p1);  
  15.   
  16.   // 输出日志并返回结果  
  17.   LogUtils.d(`testTaskWait sum: ${p.sum}  ${p1.sum}`);  
  18.   return p.sum + p1.sum;  
  19. }
复制代码
其中,Param类必要继承BaseSendable,并使用@Sendable装饰器声明。异步使命放在run()方法中执行:
  1. @Sendable  
  2. export class Param extends BaseSendable {  
  3.   private a: number = 0;  
  4.   private b: number = 0;  
  5.   public sum: number = 0;  
  6.   public enableLog: boolean = true;  
  7.   
  8.   constructor(a: number, b: number) {  
  9.     super();  
  10.     this.a = a;  
  11.     this.b = b;  
  12.   }  
  13.   
  14.   // 异步任务执行  
  15.   async run(): Promise<void> {  
  16.     this.sum = await this.add();  
  17.   }  
  18.   
  19.   async add(): Promise<number> {  
  20.     return this.a + this.b;  
  21.   }  
  22. }
复制代码
必要注意的是,taskWait()函数是一个轻量级的同步期待函数,不建议执行耗时过长的使命。对于特别耗时的使命,建议使用异步桥接函数来避免阻塞主线程和数据丢失的风险。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

科技颠覆者

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表