前言
在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的函数。