HarmonyOS - Java与Js的混合使用与交互
前言
在Harmony OS应用开发中支持JS 和 JAVA 进行开发的方式,由于每个人的开发习惯不同,掌握的开发语言不同,所以在应用开发中就会有JS与 JAVA 的混合使用的场景,需要 JS 与 JAVA 和之间的交互。Harmony OS中通过 FA 调用 PA 的机制来实现 JS 与 JAVA 和之间的交互。
HarmonyOS UI框架
在了解 FA 调用 PA 的机制之前,首先要了解什么是FA,什么是PA。HarmonyOS应用是由Ability构成的,Ability可以分为 FA(Feature Ability)和 PA(Particle Ability)两种类型。
- FA (全称 Feature Ability)支持JS 和 JAVA 的方式开发,根据开发方式可以分 JS FA 和 JAVA FA,是用于用户交互,在屏幕上显示一个用户界面,该界面用来显示所有可被用户查看和交互的内容。用户界面由UI元素构成,通常包含布局、控件等形式,且元素支持设置资源和动画。
- PA (全称 Particle Ability)一般用于后台业务逻辑的实现,分为 Data Ability 和 Service Ability,Data Ability 负责 FA 进行数据的访问,Service Ability 则负责一些后台的服务。
- Ability分类如下所示:
FA调PA机制介绍
FA 调 PA 机制,在 HarmonyOS 引擎内提供了一种通道来传递方法调用、数据返回、事件上报,可根据需要自行实现 FA 和 PA 两端的对应接口完成对应的功能逻辑。FA 一般都是选择 JS 开发的,而 PA 只支持JAVA开发,JS FA 与 JAVA PA 之间是基于RPC协议实现的进程间通信,根据系统提供的API,JS FA 将数据往平台层透传,平台层将数据转换成 C++ 类型的数据,再通过 C++ 与 JAVA 的JNI接口类,将 C++ 的数据传递到 JAVA 侧,并接收 JAVA 侧返回的数据。借助 FA 调 PA 机制,可以根据需要进行对应功能的接口拓展,实现 JS 与 JAVA 的交互。
PA端的两种实现方式
PA端包含远端调用Ability和本地调用Internal Ability两种方式。
Ability调用方式:拥有独立的Ability生命周期,FA 使用远端进程通信拉起并请求 PA 服务,适用于基本服务 PA 有多个 FA 调用或者 PA 在后台独立运行的场景。
Internal Ability调用方式:PA 与 FA 共进程, PA 和 FA 采用内部函数调用的方式进行通信,适用于对服务响应时延要求较高的场景。该方式下 PA 不支持其他 FA 访问调用。
这两种调用方式在代码中可通过abilityType来标识,更多差异如下表:
FA提供的三个JS接口:
- FeatureAbility.callAbility(OBJECT):调用PA能力。
- FeatureAbility.subscribeAbilityEvent(OBJECT, Function):订阅PA能力。
- FeatureAbility.unsubscribeAbilityEvent(OBJECT):取消订阅PA能力。
PA端提供以下两类接口:
- IRemoteObject.onRemoteRequest(int, MessageParcel, MessageParcel, MessageOption):Ability调用方式,FA使用远端进程通信拉起并请求PA服务。
- AceInternalAbility.AceInternalAbilityHandler.onRemoteRequest(int, MessageParcel, MessageParcel, MessageOption):Internal Ability调用方式,采用内部函数调用的方式和FA进行通信。
具体实现
下面通过一个Demo项目来实现JS与Java之间的交互。Demo分为两个页面,分别为JS FA 和JAVA FA,两个页面中都定义了一个随机函数,通过点击JS FA 页面的按钮,调用JAVA FA中定义的随机函数,同理通过点击JAVA FA 页面的按钮,实现JS FA中随机函数的调用。
1、效果展示
2、项目结构
- /java/slice/JavaFaAbilitySlice: 是JAVA FA页面及其控制逻辑。
- /java/JavaFaAbility: JAVA FA页面,可以由一个或多个AbilitySlice构成。
- /java/MainAbility: JS FA页面的入口,也是应用的主页面。
- /java/MessageControl: 对应功能的接口拓展,提供JS FA调用的随机函数。
- /java/ServiceInternalAbility: JAVA PA 实现java与js的通信。
- /js/pages/index/index.css: JS FA 页面样式。
- /js/pages/index/index.hml: JS FA 页面代码。
- /js/pages/index/index.js: JS FA 页面控制逻辑,JS调用JAVA PA代码。
3、JS FA 调用 JAVA PA
首先在index.js中定义随机函数并实现页面控制逻辑,然后实现PA的订阅 await FeatureAbility.subscribeAbilityEvent(action, function (result)订阅 JAVA PA,在订阅返回的结果中进行接口的拓展,根据返回的消息是否是“callJs”区分是JS调用JAVA ,还是 JAVA 调用 JS,可以根据具体业务约定来实现,详细代码如下:
import router from '@system.router';import featureAbility from '@ohos.ability.featureAbility';export default { data: { title: "", num: "随机数", java_fa: "跳转到JavaFA" }, onInit() { this.title = this.$t('这里是JS FA页面'); }, cameraError() { prompt.showToast({ message: "授权失败!" }); }, random() { var random = Math.random() * 10; this.num = Math.ceil(random); }, jumpToJavaFA() { var javaFA = { "want": { "bundleName": "com.example.camerademo.hmservice", "abilityName": "com.example.camerademo.JavaFaAbility", }, }; featureAbility.startAbility(javaFA, (err, data) => { if (err) { console.error('Operation failed. Cause:' + JSON.stringify(err)); return; } console.info('Operation successful. Data: ' + JSON.stringify(data)) }); }, //初始化数据 initAction: function (code) { var actionData = {}; var action = {}; action.bundleName = "com.example.camerademo.hmservice"; action.abilityName = "com.example.camerademo.ComputeInternalAbility"; action.messageCode = code; action.data = actionData; action.abilityType = 1; action.syncOption = 0; return action; }, //订阅JAVA PA callJavaSubscribe: async function () { try { var action = this.initAction(1001); action.actionDate = {}; var that = this; var result = await FeatureAbility.subscribeAbilityEvent(action, function (result) { console.info(" result info is: " + result); var responseData = JSON.parse(result).data; if (responseData.msg == "callJs") { that.random(); }else{ that.showToast(" JavaFA随机数: " + responseData.msg); } }); //this.showToast(" subscribe result " + result); console.info(" subscribe result = " + result); } catch (pluginError) { console.error("subscribe error : result= " + result + JSON.stringify(pluginError)); } }, showToast: function (msg) { prompt.showToast({ message: msg }); }}
4、实现 JAVA PA
在ServiceInternalAbility中实现JAVA PA,对JS FA 传过来的消息进行解析并响应对应的JAVA 接口,并且定义了javaCallJsMsg () 的方法,利用 JS FA 与 PA 通信的通道对外提供给 JAVA 调用 JS 的功能,详细代码如下:
public class ServiceInternalAbility extends AceInternalAbility { private static final String BUNDLE_NAME = "com.example.camerademo.hmservice"; private static final String ABILITY_NAME = "com.example.camerademo.ComputeInternalAbility"; private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD000F00, "app Log:"); private static final int DEFAULT_TYPE = 0; private static ServiceInternalAbility instance; private IRemoteObject notifier; public ServiceInternalAbility() { super(BUNDLE_NAME, ABILITY_NAME); } /** * 解析JS FA 订阅 PA 发送的消息,并返回订阅结果 */ public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) { notifier = data.readRemoteObject(); switch (code) { case 1001: jsCallJavaMsg(notifier); break; default: reply.writeString("service not defined"); return false; } return true; } /** * 对外部java调用JS对应接口 */ public void javaCallJsMsg(String msg, CallBack callBack) { MessageParcel notifyData = MessageParcel.obtain(); notifyData.writeString("{\"msg\":\"" + msg + "\"}"); try { if (notifier != null && callBack != null) { notifier.sendRequest(DEFAULT_TYPE, notifyData, MessageParcel.obtain(), new MessageOption()); callBack.response(msg); } } catch (RemoteException exception) { HiLog.info(LABEL_LOG, "%{public}s", "replyMsg RemoteException !"); } finally { notifyData.reclaim(); } } /** * js调用JAVA对应接口 */ private void jsCallJavaMsg(IRemoteObject notifier) { MessageParcel notifyData = MessageParcel.obtain(); notifyData.writeString("{\"msg\":\"" + MessageControl.getInstance().randomNum() + "\"}"); try { notifier.sendRequest(DEFAULT_TYPE, notifyData, MessageParcel.obtain(), new MessageOption()); } catch (RemoteException exception) { HiLog.info(LABEL_LOG, "%{public}s", "replyMsg RemoteException !"); } finally { notifyData.reclaim(); } } /** * 实现PA的单例模式,提供给外部使用 */ public static ServiceInternalAbility getInstance() { if (instance == null) { synchronized (ServiceInternalAbility.class) { if (instance == null) { instance = new ServiceInternalAbility(); } } } return instance; } /** * 注册PA,需要在MainAbility的 onStart() 中注册 */ public void register() { this.setInternalAbilityHandler(this::onRemoteRequest); } /** * 注销PA,需要在MainAbility的 onStop() 中注销 */ public void deregister() { this.setInternalAbilityHandler(null); } public interface CallBack { void response(String msg); }}
然后在 MessageControl 里面实现随机函数,以便提供给JS调用,详细代码如下:
public class MessageControl { private static MessageControl instance; public String msgData; public static MessageControl getInstance() { if (instance == null) { synchronized (MessageControl.class) { if (instance == null) { instance = new MessageControl(); } } } return instance; } /* * 随机函数 * */ public String randomNum() { int num = (int) (Math.random() * 100); msgData = String.valueOf(num); return msgData; }}
5、实现 JAVA 调用 JS
通过ServiceInternalAbility 提供给外部调用的javaCallJsMsg()方法,在JavaFaAbilitySlice中实现JAVA 调用 JS 的逻辑,详细代码如下:
public class JavaFaAbilitySlice extends AbilitySlice { @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_java_fa); Button button = findComponentById(ResourceTable.Id_button); Button button2 = findComponentById(ResourceTable.Id_button2); Text textNum = findComponentById(ResourceTable.Id_num); textNum.setText(MessageControl.getInstance().msgData); // 点击获取随机数 button.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { String num = MessageControl.getInstance().randomNum(); textNum.setText(num); } }); // 点击按钮调用JS函数 button2.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { ServiceInternalAbility.getInstance().javaCallJsMsg("callJs", new ServiceInternalAbility.CallBack() { @Override public void response(String msg) { new ToastDialog(getContext()) .setText(msg) .setAlignment(LayoutAlignment.CENTER) .show(); } }); } }); }}
总结
虽然通过FA 调用 PA 的机制可以让 JAVA 与 JS进行交互,但是前提是必须让 JS FA 主动订阅 PA,如果 JS FA 取消订阅 PA,或者 PA 服务由于某种原因挂掉了,Java 无法主动与JS建立通信,JAVA 与 JS 之间就失去了通信无法调用JS的函数。