HarmonyOS - Java与Js的混合使用与交互 原创 精华

发布于 2022-5-12 11:56
浏览
3收藏

作者:陈忠蔚

前言

在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分类如下所示:
    HarmonyOS - Java与Js的混合使用与交互-开源基础软件社区

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来标识,更多差异如下表:
差异项 Ability方式 Internal Ability方式
abilityType标识 0 1
是否需要在config.json中声明
是否需要在FA中注册
是否允许其他FA调用 HarmonyOS - Java与Js的混合使用与交互-开源基础软件社区

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.效果展示

HarmonyOS - Java与Js的混合使用与交互-开源基础软件社区

2.项目结构

HarmonyOS - Java与Js的混合使用与交互-开源基础软件社区

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

更多原创内容请关注:中软国际 HarmonyOS 技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-5-12 14:07:21修改
6
收藏 3
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐