鸿蒙JS FA调用PA 原创 精华

NL_AIDC_XJS
发布于 2022-2-23 16:02
浏览
3收藏

一、目标

实现通过JS FA调用PA的能力

二、效果

两个数字相乘的效果

三、基础知识

3.1、什么是FA?什么是PA?

要说明FA和PA,首先需要了解:Ability是什么?
Ability是应用所具备能力的抽象,是系统调度应用的最小单元,也是应用程序的重要组成部分。一个应用可以具备多种能力(多个Ability),HarmonyOS支持应用以Ability为单位进行部署,Ability可以分为FA和PA。
FA:Feature Ability,带有界面,支持Page Ability,Page模板是FA唯一支持的模板,用于提供与用户交互的能力。一个Page实例可以包含一组相关的页面,每一个页面用一个AbilitySlice实例表示。
PA:Particle Ability,不带界面,支持Service Ability和Data Ability,其中Service模板用于提供后台运行任务的能力,Data模板用于对外部提供统一的数据访问能力。
鸿蒙JS FA调用PA-鸿蒙开发者社区

3.2、远端调用Ability和本地调用Internal Ability两种调用方式有什么区别?

  • Ability:拥有独立的Ability生命周期,FA使用远端进程通信拉起并请求PA服务,适用于基本服务供多FA调用或者服务在后台独立运行的场景。
  • Internal Ability:与FA共进程,采用内部函数调用的方式和FA进行通信,适用于对服务响应时延要求较高的场景。该方式下PA不支持其他FA访问调用。

Ability和InternalAbility差异项

差异 Ability Internal Ability
JS端(abilityType) 0 1
是否需要在config.json的abilities中为PA添加声明 需要(有独立的生命周期) 不需要(与FA共生命周期)
是否需要在FA中注册 不需要 需要
继承的类 ohos.aafwk.ability.Ability ohos.ace.ability.AceInternalAbility
是否允许被其他FA访问调用

3.3、FA调PA机制介绍

该机制在HarmonyOS引擎内提供了一种通道来传递方法调用、数据返回、事件上报。具体描述:JS端与Java端通过接口扩展机制进行通信,通过bundleName和abilityName来进行关联。在FeatureAbility Plugin收到JS调用请求后,系统根据开发者在JS指定的abilityType,Ability或Internal Ability,来选择对应的方式进行处理。开发者在onRemoteRequest()中实现PA提供的业务逻辑,不同的业务通过业务码来区分。

3.4、官方文档

基于JS扩展的类Web开发范式的方舟开发框架提供JS FA调用Java PA机制

3.5、综合案例

综合示例

四、JS FA调用Java PA实践

4.1、在实战之前先说明几个接口

4.1.1、FA提供的JS接口

接口 描述 备注
FeatureAbility.callAbility(OBJECT) JS FA 调用PA(Particle Ability)提供的能力 SDK接口
FeatureAbility.subscribeAbilityEvent(OBJECT, Function) 订阅PA能力 SDK接口
FeatureAbility.unsubscribeAbilityEvent(OBJECT) 取消订阅PA能力 SDK接口

4.1.2、PA端提供以下接口

接口 描述 备注
IRemoteObject.onRemoteRequest(int, MessageParcel, MessageParcel, MessageOption) Ability调用方式,FA使用远端进程通信拉起并请求PA服务 SDK接口
AceInternalAbility.AceInternalAbilityHandler.onRemoteRequest(int, MessageParcel, MessageParcel, MessageOption) Internal Ability调用方式,采用内部函数调用的方式和FA进行通信 SDK接口

4.2、实战

需求:在页面(JS FA)上输入两个数字,点击计算,通过Java PA进行计算后返回结果,将显示在屏幕上。

说明:本实战演示两个数的计算,如果说实现两个数的计算其实不需要这么复杂的逻辑,直接在JS层就可以完成,但本实战只是为了演示JS FA调用PA的能力,实现JS FA调用PA中的方法,也就是不同语音直接的接口调用,并将结果返回JS FA中显示,重点介绍Internal Ability实现方式。

4.2.1、创建项目

说明:通过DevEco Studio创建 JS 类型的项目
鸿蒙JS FA调用PA-鸿蒙开发者社区
鸿蒙JS FA调用PA-鸿蒙开发者社区

提醒:如果你是真机调试,建议在AGC(AppGallery Connect)平台上创建相关的项目和应用,这样在开发调试阶段,可以通过 Automatically generate signing 自动生成临时的认证和签名完成调试。官方介绍:使用真机调试
1、在AGC平台上创建项目和HarmonyOS 应用
鸿蒙JS FA调用PA-鸿蒙开发者社区
2、在project的debug环境下通过Automatically generate signing 自动生成调试签名和认证
鸿蒙JS FA调用PA-鸿蒙开发者社区

4.2.2、项目目录介绍

鸿蒙JS FA调用PA-鸿蒙开发者社区
下面简单介绍下JS FA调用PA的项目目录,从上面截图可以看出,项目中包含java目录和js目录,我们分开来介绍。

4.2.2.1、java相关介绍

首先我们先看下java目录下,包括 MyApplication、MainAbility,其中MyApplication是应用启动时调用的类,重点来看下MainAbility,代码如下:

public class MainAbility extends AceAbility {
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
    }

    @Override
    public void onStop() {
        super.onStop();
    }
}

很明显可以看到,MainAbility是继承AceAbility,而AceAbility类是JS FA在HarmonyOS上运行环境的基类,继承自Ability。从实现的接口上看没有类似于加载Page的设置,那系统是如何加载JS FA呢?

JS FA生命周期事件分为应用生命周期和页面生命周期,应用通过AceAbility类中setInstanceName(String name)接口设置该Ability的实例资源,并通过AceAbility窗口进行显示以及全局应用生命周期管理。

setInstanceName(String name) :参数name指实例名称,实例的名称与config.json文件中的module.js.name的值对应。创建项目时默认的实例名称是default,因为是默认值,如果使用了缺省值default,则无需调用此接口,就可以默认加载default下的页面。如果你修改了实例名称,则需要在应用Ability实例的onStart()中调用setInstanceName(String name),并将参数“name”设置为修改后的实例名称。
需要注意:在调用setInstanceName(String name)接口时,需要在Ability实例onStart()接口中的super.onStart()之前调用。例如,js的实例名称为:“JSShopping”则调用方式如下:

public class MainAbility extends AceAbility {
    @Override
    public void onStart(Intent intent) {
        setInstanceName("JSShopping");  
        super.onStart(intent);
    }
}
4.2.2.2、js相关介绍

官方介绍:基于JS扩展的类Web开发范式
项目目录中
|–i18n:存放多语言的json文件
|–pages:存放多个页面,每个页面由hml、css和js文件组成,HML(HarmonyOS Markup Language)是一套类HTML的标记语言。通过组件、事件构建出页面的内容。页面具备数据绑定、事件绑定、列表渲染、条件渲染等高级能力。
|–common:存放图片等资源文件
用于用户交互的界面就位于 main > js > default > pages > index > index.hml,此文件定义了index页面的布局、index页面中用到的组件,以及这些组件的层级关系。例如:index.hml文件中包含了一个text组件,内容为“Hello World”文本。

<div class="container">
    <text class="title">
        {{ $t('strings.hello') }} {{ title }}
    </text>
</div>

之后我们就会修改此界面的布局,实现两个输入框和一个计算按钮,实现用户输入两个数字,计算两个数的乘积。此处省略对应的 index.js、index.css的介绍,在文章最后会附上相关的代码。

4.2.3、创建JS FA

4.2.3.1、index.hml
<div class="container">
    <div class="input_content">
        <input type="date" class="input_str" placeholder="{{$t('strings.input_number')}}" onchange="oneNumberChange"></input>
        <text class="text_multiply">*</text>
        <input type="date" class="input_str" placeholder="{{$t('strings.input_number')}}" onchange="secondNumberChange"></input>
    </div>
    <button class="btn" type="text" onclick="calculateCount">{{$t('strings.calculate')}}</button>
</div>

解析:页面上显示两个输入框(input),类型为数字(date),一个按钮(button),按钮的文本为“计算”,在输入框中绑定了onchange的监听器,用于用户输入的数字,按钮上绑定了点击事件onclick,用于触发只需JS FA调用PA,完成两个数相乘的计算。

4.2.3.2、index.js
import prompt from '@system.prompt';

// 定义常量 0-Ability、1-Internal Ability
const ABILITY_TYPE_EXTERNAL = 0;
const ABILITY_TYPE_INTERNAL = 1;
// 接口调用同步或者异步
const ACTION_SYNC = 0;
const ACTION_ASYNC = 1;

// 业务码
const ACTION_MESSAGE_CODE_CALCULATE = 1001;// 计算
const SUCCESS = 0;

export default {
    data: {
        title: "",
        oneNumber: 0,
        secondNumber: 0
    },
    onInit() {
        this.title = this.$t('strings.world');
    },

    oneNumberChange(e) {
        this.oneNumber = e.value;
    },

    secondNumberChange(e) {
        this.secondNumber = e.value;
    },

    // 计算两个数的乘积
    calculateCount: async function(){
        var actionData = {};
        actionData.oneNumber = this.oneNumber;
        actionData.secondNumber = this.secondNumber;
        // 调用PA action信息
        var action = {};

        action.bundleName = 'com.nlscan.example';
        // 在JAVA中创建的PA
        action.abilityName = 'com.nlscan.example.ComputeServiceAbility';
        action.messageCode = ACTION_MESSAGE_CODE_CALCULATE;
        action.data = actionData;
        action.abilityType = ABILITY_TYPE_INTERNAL;
        action.syncOption = ACTION_SYNC;

        // 调用PA
        var resultData = await FeatureAbility.callAbility(action);
        // 解析返回的结果
        var result = JSON.parse(resultData);
        if (result.code == SUCCESS) {
            prompt.showToast({
                message: result.abilityResult,
                duration: 1000
            });
        }
    }

}

  • oneNumberChange和secondNumberChange用于监听输入框中的内容变化,并缓存变化的内容。
  • calculateCount触发JS FA调用PA,FeatureAbility.callAbility(action)其中action设置了相关的参数如下表所示
参数名称 类型 必填 说明
bundleName string Ability包的名称,需要与PA端匹配,区分大小写。
abilityName string Ability的名称,需要与PA端匹配,区分大小写。
messageCode number Ability操作码,操作码定义PA的业务功能,需要与PA端约定
abilityType number Ability类型,对应PA端不同的实现方式: 0:Ability,拥有独立的Ability生命周期,FA使用远端进程通信拉起并请求PA服务,适用于提供基本服务供多FA调用或者在后台独立运行的场景;1:Internal Ability,与FA共进程,采用内部函数调用的方式和FA通信,适用于对PA响应时延要求较高的场景,不支持其他FA访问调用能力。
data Object 发送到Ability的数据,根据不同的业务携带相应的业务数据,数据字段名称需要与PA端约定
syncOption number PA侧请求消息处理同步/异步选项,非必填,默认使用同步方式。当前异步方式仅支持AbilityType为Internal Ability类型。0:同步方式,默认方式。1:异步方式。

FeatureAbility.callAbility(action)返回值

类型 说明
Promise<string> Promise中包含PA返回的结果数据,结果格式为JSON字符串。
4.2.3.3、index.css
.container {
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100%;
}

.input_content {
    width: 100%;
    height: 250px;
    align-items: center;
    justify-content: center;
    flex-direction: column;
}

.text_multiply{
    text-color: black;
    font-size: 30px;
    margin-bottom: 10px;
}

.input_str {
    width: 80%;
    margin-bottom: 25px;
}

.btn {
    text-color: white;
    font-size: 20px;
    font-style: normal;
    background-color: aquamarine;
    width: 50%;
    height: 50px;
}

4.2.4、创建Java PA

4.2.4.1、PA类实现

说明:在main->java->com.nlscan.example目录下创建PA实现类ComputeServiceAbility,ComputeServiceAbility类继承AceInternalAbility,并实现AceInternalAbility.AceInternalAbilityHandler的接口boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption messageOption),在onRemoteRequest中通过JS与PA约定的业务码code区分业务,并实现相关逻辑,代码如下:

import com.nlscan.example.module.RequestData;
import ohos.ace.ability.AceInternalAbility;
import ohos.app.AbilityContext;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.rpc.IRemoteObject;
import ohos.rpc.MessageOption;
import ohos.rpc.MessageParcel;
import ohos.rpc.RemoteException;
import ohos.utils.zson.ZSONObject;

import java.util.HashMap;
import java.util.Map;

public class ComputeServiceAbility extends AceInternalAbility implements AceInternalAbility.AceInternalAbilityHandler {
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, "app_tag");
    private static final String BUNDLE_NAME = "com.nlscan.example";
    private static final String ABILITY_NAME = "com.nlscan.example.ComputeServiceAbility";
    // 返回JS的结果码
    private static final int SUCCESS = 0;
    private static final int ERROR = 1;
    // 与JS约定的业务码
    private static final int CALCULATE = 1001;

    private static ComputeServiceAbility mInstance;
    private AbilityContext mContext;
    public ComputeServiceAbility() {
        super(BUNDLE_NAME, ABILITY_NAME);
    }


    public static void register(AbilityContext context){
        mInstance = new ComputeServiceAbility();
        mInstance.mContext = context;
        mInstance.setInternalAbilityHandler(mInstance);
    }

    public static void unregister() {
        if (mInstance == null) {
            return;
        }
        mInstance.mContext = null;
        mInstance.setInternalAbilityHandler(null);
    }


    @Override
    public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption messageOption) {
        HiLog.info(LABEL, "code:" + code);
        switch (code) {
            case CALCULATE: {
                // 执行计算
                String dataStr = data.readString();
                HiLog.info(LABEL, "dataStr:" + dataStr);
                RequestData requestData = new RequestData();
                requestData = ZSONObject.stringToClass(dataStr, RequestData.class);
                int count = requestData.getOneNumber() * requestData.getSecondNumber();
                Map<String, Object> result = new HashMap<>();
                result.put("code", SUCCESS);// 返回结果码
                result.put("abilityResult", count);
                // 同步返回
                if (messageOption.getFlags() == MessageOption.TF_SYNC) {
                    reply.writeString(ZSONObject.toZSONString(result));
                } else {
                    // 异步返回
                    MessageParcel responseData = MessageParcel.obtain();
                    responseData.writeString(ZSONObject.toZSONString(result));
                    IRemoteObject remoteObject = reply.readRemoteObject();
                    try {
                        remoteObject.sendRequest(0, responseData, MessageParcel.obtain(), new MessageOption());
                    } catch (RemoteException e) {
                       HiLog.error(LABEL, "error:" +  e.getMessage());
                       return false;
                    } finally {
                        responseData.reclaim();
                    }
                }
                break;
            }

            default:
                Map<String, Object> result = new HashMap<>();
                result.put("code", ERROR);// 返回结果码
                reply.writeString(ZSONObject.toZSONString(result));
                return false;
        }
        return true;
    }
}
4.2.4.2、数据模型 RequestData

说明:在解析JS返回的数据中,根据数据结构定义数据模式类,代码如下:

public class RequestData {

    private int oneNumber;
    private int secondNumber;

    public int getOneNumber() {
        return oneNumber;
    }

    public void setOneNumber(int oneNumber) {
        this.oneNumber = oneNumber;
    }

    public int getSecondNumber() {
        return secondNumber;
    }

    public void setSecondNumber(int secondNumber) {
        this.secondNumber = secondNumber;
    }
}
4.2.4.3 注册和反注册PA

在MainAbility的生命周期中管理PA,onStart()中注册,在onStop中反注册,代码如下:

import ohos.ace.ability.AceAbility;
import ohos.aafwk.content.Intent;

public class MainAbility extends AceAbility {
    @Override
    public void onStart(Intent intent) {
        ComputeServiceAbility.register(this);
        super.onStart(intent);
    }

    @Override
    public void onStop() {
        ComputeServiceAbility.unregister();
        super.onStop();
    }
}

到此JS FA调用PA以 InternalAbility类型实现的功能就完成了。

五、使用工具自动生成JS FA调用PA

如果你使用的PA实现方式为InternalAbility类型,可以通过工具直接生成相关代码,具体可以查看:是使用工具自动生成JS FA调用PA代码:js2java-codegen
注意:js2java-codegen工具所支持的FA调用PA实现方式为InternalAbility类型,目前尚不支持Ability类型。

六、FA调用PA常见问题的FAQ

6.1、JS调用FeatureAbility.callAbility(OBJECT)返回:“Internal ability not register.”。
回答:返回该错误说明JS接口调用请求未在系统中找到对应的InternalAbilityHandler进行处理,因此需要检查以下几点是否正确执行:

  • 在AceAbility继承类中对AceInternalAbility继承类执行了register方法。
  • JS侧填写的bundleName和abilityName与AceInternalAbility继承类构造函数中填写的名称保持相同,大小写敏感。
  • 检查JS端填写的abilityType(0:Ability; 1:Internal Ability),确保没有将AbilityType缺省或误填写为Ability方式。

七、感谢

如果您能看到最后,还希望您能动动手指点个赞,一个人能走多远关键在于与谁同行,我用跨越山海的一路相伴,希望得到您的点赞。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-2-23 17:42:57修改
6
收藏 3
回复
举报
2条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

期待效果展示

回复
2022-2-23 16:49:06
Soon_L
Soon_L

学习了

回复
2022-2-25 12:56:46
回复
    相关推荐