无需手动撰写,HarmonyOS工具自动生成代码,真香!
JS UI框架提供了JS FA(Feature Ability)调用Java PA(Particle Ability)的机制,该机制提供了一种通道来传递方法调用、处理数据返回以及订阅事件上报。
在往期的《JS UI框架下FA与PA是如何交互的》一文中,给大家介绍了如何通过利用FA、PA交互机制来完成基于JS UI框架的应用开发。但是,开发者在实操过程中,都遇到一个共同的问题,就是需要手动撰写大量模板代码,且模板代码可能与业务代码相互耦合,使得代码可维护性和可读性较差。 于是,js2javacodegen工具应运而生。
本期,小编将通过开发一个简单的计算器应用,阐述JS UI框架下,如何使用js2javacodegen工具自动生成JS FA调用Java PA的模板代码。
注:以下内容中涉及到的 “FA调用PA”,均是指JS UI框架下JS FA调用Java PA。
js2javacodegen是HarmonyOS SDK中Toolchains工具链从2.2.0.3版本开始提供的自动生成FA调用PA代码的辅助开发工具。它可以根据用户源码自动生成FA调用PA时所需的Java和JS模板代码,该模板代码与用户编写的业务代码相互分离,降低了代码的耦合。
目前,js2javacodegen工具所支持的FA调用PA实现方式为InternalAbility类型,Ability类型尚不支持。
说明 :当前JS FA调用Java PA的机制中,提供了Ability和InternalAbility两种调用方式:
- Ability:拥有独立的Ability生命周期,FA使用远端进程通信拉起并请求PA服务,适用于基本服务供多FA调用或者服务在后台独立运行的场景。
- InternalAbility:与FA共进程,采用内部函数调用的方式和FA进行通信,适用于对服务响应时延要求较高的场景。该方式下PA不支持其他FA访问调用。
更多JS FA调用Java PA的机制官网文档
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-js-fa-call-pa-0000001050435961
由于该工具从HarmonyOS SDK中Toolchains的2.2.0.3版本开始支持,所以请先把DevEco Studio更新至最新的2.2 Beta2版本。
DevEco Studio官网下载链接:
https://developer.harmonyos.com/cn/develop/deveco-studio#download
1. 新建工程
在最新版的DevEco Studio 2.2 Beta2下,新建一个包含JS的手机项目。
2. 工具配置
开发者需在进行代码生成模块下的build.gradle中进行编译设置及开关控制。
编译参数位于ohos > defaultConfig下,开发者需用以下方式设置JS模板代码生成路径,即’jsOutputDir’对应的值。代码如下:
// 定义JS模板生成路径
def jsOutputDir = project.file("src/main/js/default/generated").toString()
ohos {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
// JS模板代码生成路径赋值
arguments = ['jsOutputDir': jsOutputDir]
}
}
}
}
工具开关位于ohos下,值设为true则启用工具,false或不进行配置则不启用工具。配置代码如下:
ohos {
compileOptions {
// 此处为启用js2java-codegen工具的开关。值为true则启用工具,false则不启用工具。
f2pautogenEnabled true
}
}
3. PA侧代码编写
开发者需在PA侧用Java语言手动编写实现计算器业务逻辑的InternalAbility类,用于接收FA侧传来的运算表达式,并对表达式的合法性进行检验。然后通过单独编写一个工具类来完成对运算表达式的计算,并由InternalAbility来调用,将计算结果返回FA侧。
本示例中,开发者通过新建CalculateService类实现计算器的业务逻辑,并对CalculateService类本身添加@InternalAbility注解,表示该类为InternalAbility类,并且用参数指定该类注册到同包中的MainAbility类。然后通过calculate()方法来实现计算器的基本操作,包括入参检验、调用工具类实现运算表达式的计算、捕获异常并返回结果,部分示例代码如下所示:
package com.example.simplecalculatorfapa;
import com.example.simplecalculatorfapa.utils.Util;
import ohos.annotation.f2pautogen.InternalAbility;
import java.util.EmptyStackException;
import java.util.regex.Pattern;
// 注册到同一个包下的MainAbility类中
@InternalAbility(registerTo = "com.example.simplecalculatorfapa.MainAbility")
public class CalculateService {
public String calculate(String exp) {
// 排除不需计算就可发现的非法情况,此处列出一种为例
if (exp.isEmpty()) {
return "NoResult";
}
// ...
// 使用工具类进行计算,捕获可能出现的异常
String result;
try {
result = Util.getResult(exp);
} catch (NumberFormatException | ArithmeticException | EmptyStackException e) {
return "Wrong";
}
// 返回合法结果
return result;
}
}
工具注解说明:js2javacodegen工具通过注解来获取信息并生成开发者所需的代码。因此用户如果想使用该工具辅助开发,则需要了解以下三种注解的用法:
@InternalAbility注解:类注解,用于被使用作InternalAbility的、包含实际业务代码的类(简称InternalAbility类)。包含一个参数:registerTo,参数值为需要注册的Ability类全名。如下用例表示Service类是一个InternalAbility类,注册到位于com.example包中的、名为Ability的Ability类。
@InternalAbility (registerTo = “com.example.Ability”)
public class Service{}
@ExportIgnore注解:方法注解,用于InternalAbility类中的某些方法,表示该方法不暴露给JS侧来调用,仅对public方法有效。如下用例表示service方法不会被暴露给JS侧。
@ExportIgnore public int service(int input) {return input;}
@ContextInject注解:用于AbilityContext上的注解。该类由HarmonyOS的Java API提供,开发者可通过它获取API中提供的信息。如下用例表示开发者可以借助abilityContext对象获取API中提供的信息。
@ContextInject AbilityContext abilityContext;
4. 编译
编写完InternalAbility类的业务代码后,下面FA调用PA的模板代码生成工作就交给js2javacodegen工具吧!
开发者只需点击菜单栏中的Build > Build HAP(s)/APP(s) > Build HAP(s),即可完成对项目的编译,同时js2javacodegen工具会在编译过程中完成FA调用PA通道代码的生成。 编译过程会生成Java和JS的模板代码。
① 自动生成的Java模板代码位于entry > build > generated> source > annotation > debug 下。部分Java模板代码如下所示:
public boolean onRemoteRequest(int code,MessageParcel data, MessageParcel reply,
MessageOption option) {
Map<String, Object> result = new HashMap<String,Object>();
switch(code) {
case OPCODE_calculate:{
java.lang.String zsonStr =data.readString();
ZSONObject zsonObject =ZSONObject.stringToZSON(zsonStr);
java.lang.String exp =zsonObject.getObject("exp",java.lang.String.class);
result.put("code", SUCCESS);
result.put("abilityResult",service.calculate(exp));
break;}
default:reply.writeString("Opcode is not defined!");
return false;
}
return sendResult(reply,result, option.getFlags() == MessageOption.TF_SYNC);
}
rivate boolean sendResult(MessageParcel reply,Map<String, Object> result, boolean isSync) {
if (isSync) {
reply.writeString(ZSONObject.toZSONString(result));
} else {
MessageParcel response =MessageParcel.obtain();
response.writeString(ZSONObject.toZSONString(result));
IRemoteObject remoteReply =reply.readRemoteObject();
try {
remoteReply.sendRequest(0, response,MessageParcel.obtain(), new MessageOption());
response.reclaim();
} catch (RemoteExceptionexception) {
return false;
}
}
return true;
}
② 自动生成的JS模板代码位于开发者在编译设置中设置的路径,名称与InternalAbility类的名称相对应。自动生成的JS模板代码如下所示:
// This file is automatically generated. Do not modify it!
const ABILITY_TYPE_EXTERNAL = 0;
const ABILITY_TYPE_INTERNAL = 1;
const ACTION_SYNC = 0;
const ACTION_ASYNC = 1;
const BUNDLE_NAME = 'com.example.simplecalculatorfapa';
const ABILITY_NAME = 'com.example.simplecalculatorfapa.CalculateServiceStub';
const OPCODE_calculate = 0;
const sendRequest = async (opcode, data) => {
var action = {};
action.bundleName = BUNDLE_NAME;
action.abilityName = ABILITY_NAME;
action.messageCode = opcode;
action.data = data;
action.abilityType = ABILITY_TYPE_INTERNAL;
action.syncOption = ACTION_SYNC;
return FeatureAbility.callAbility(action);
}
class CalculateService {
async calculate(exp) {
if (arguments.length != 1) {
throw new Error("Method expected 1 arguments, got " + arguments.length);
}
let data = {};
data.exp = exp;
const result = await sendRequest(OPCODE_calculate, data);
return JSON.parse(result);
}
}
export default CalculateService;
5. FA侧代码编写
FA侧的内容包含“由html与css代码编写的静态页面”及“实现按钮与方法动态关联的JS代码”。首先,开发者需在开头引入由js2javacodegen工具自动生成的JS模板代码的FA接口类,然后实现计算器按钮对应的方法。由于“=”按钮对应的方法调用了PA侧的计算功能,因此需在该方法中新建FA接口示例,并调用对应方法(名称与InternalAbility类中需要被调用的方法名称相同),并将输入框的值传入,将返回值打印在结果框中。
本示例中,开发者通过在开头引入由js2javacodegen工具自动生成的JS模板代码的CalculateService接口类(from后的值需要与编译设置中的路径进行统一,JS模板代码文件名称与InternalAbility类名相同),然后用data实现输入框与结果框的动态,并在等号按钮对应的calculate()方法中新建接口实例,通过调用接口类的calculate()方法,将输入框值传入,并把返回值赋给结果框。示例代码如下所示:
// 引入calculateService类
import CalculateService from '../../generated/CalculateService.js';
export default {
// 用于实现输入框和结果框的动态变化
data: {
input: "", // 输入框内容
result: "" // 结果框内容
},
// 不需要调用PA的按钮功能实现,此处列出退格键为例
deleteOne() {
this.result = "";
this.input = this.input.substr(0, this.input.length-1);
},
// ...
// 由等号触发的方法,调用PA
calculate() {
// 新建CalculateService示例
var service = new CalculateService();
// 调用calculate方法,传入输入框内容并将返回结果赋值给结果框
service.calculate(this.input)
.then((data) => {
this.result = data["abilityResult"];
});
}
}
启动手机模拟器,并运行程序,即可生成结果。效果展示如下:
至此,一个运用js2javacodegen工具开发的计算器Demo就完成了。该工具引入的代码生成技术,大大提升了跨语言调用场景的开发效率,让HarmonyOS开发者更能专注业务开发,提升开发体验。
更多js2java-codegen工具内容,请点击下方官网链接:https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-js2java-codegen-0000001171039385
作者:guxinmeng