鸿蒙JS FA调用PA 原创 精华
一、目标
实现通过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模板用于对外部提供统一的数据访问能力。
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 类型的项目
提醒:如果你是真机调试,建议在AGC(AppGallery Connect)平台上创建相关的项目和应用,这样在开发调试阶段,可以通过 Automatically generate signing 自动生成临时的认证和签名完成调试。官方介绍:使用真机调试
1、在AGC平台上创建项目和HarmonyOS 应用
2、在project的debug环境下通过Automatically generate signing 自动生成调试签名和认证
4.2.2、项目目录介绍
下面简单介绍下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方式。
七、感谢
如果您能看到最后,还希望您能动动手指点个赞,一个人能走多远关键在于与谁同行,我用跨越山海的一路相伴,希望得到您的点赞。
期待效果展示
学习了