> 文档中心 > HarmonyOS - Java与Js的混合使用与交互

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

奇石交易网