HarmonyOS JS FA调用PA新方式

开发 OpenHarmony
JS FA调用Java PA一般通过FeatureAbility这种传统方式调用。本文先回顾传统方式,在传统方式的痛点上引入两种新方式。

[[431269]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

简介

​ JS FA调用Java PA一般通过FeatureAbility这种传统方式调用。本文先回顾传统方式,在传统方式的痛点上引入两种新方式。

传统方式

方法说明

常见的JS FA调用Java Pa一般通过以下三个方法

(1) FeatureAbility.callAbility(OBJECT):调用PA能力。

(2) FeatureAbility.subscribeAbilityEvent(OBJECT, Function):订阅PA能力。

(3) FeatureAbility.unsubscribeAbilityEvent(OBJECT):取消订阅PA能力。

1.能常用于同步调用方法

比如:

  1. //实现(Java) 
  2. public int add(int a, int b) { 
  3.     return a + b; 
  4.  
  5. //调用: 
  6. add: async function (data) { 
  7.     let params = getRequestAction(ACTION_MESSAGE_CODE_ADD); 
  8.     params.data = data; 
  9.     var ret = await FeatureAbility.callAbility(params) 
  10.     console.log("result:" + JSON.parse(ret) + "," + ret) 
  11.     return JSON.parse(ret) 

2.常用于调用异步方法,因为(2)是通过监听的方式实现异步回调,所以在不需要监听时最好调用(3)取消

比如:

  1. //实现(Java) 
  2. public void addAsync(int a, int b, Callback cb) { 
  3.     new Thread(() -> { 
  4.         int ret = a + b;//假设这里是一个耗时操作 
  5.         cb.reply(ret ); 
  6.     }).start(); 
  7.  
  8. //调用(JS): 
  9. addAsync: async function (data,func) { 
  10.     let params = getRequestAction(ACTION_MESSAGE_CODE_ADDAsync); 
  11.     params.data = data; 
  12.     FeatureAbility.subscribeAbilityEvent(params, (callbackData) => { 
  13.         var callbackJson = JSON.parse(callbackData); 
  14.         //console.log('getAirlineOpenCity is: ' + JSON.stringify(callbackJson.data)); 
  15.         func(callbackJson.data) 
  16.     }) 
  17. }, 

实现步骤

在开发过程中应用中常用Internal Ability,现以Internal Ability说明。

Internal Ability需要实现的步骤:

(1)在java中创建java类继承AceInternalAbility,设置setInternalAbilityHandler回调,在回调方法onRemoteRequest中通过命令码code匹配对应的方法名,Parcel中解析方法参数;

(2)在AbilityPackage或者Ability的生命周期对java类对象进行注册和反注册;

(3)在JS中创建对应JS类,调用FeatureAbility.callAbility,FeatureAbility.subscribeAbilityEvent传递命令码和方法参数;

痛点

从实现步骤看出,建立Js FA到Java PA的通道,比较繁琐,以下操作如果能够由系统自动完成,那么用户只关注java端方法实现、js端调用,使用就简便很多。

(1)进行注册,反注册;

(2)解析方法名,方法参数;

(3)返回值需要通过parcel进行回传;

注意点

1.java给js端返回结果(回消息)时,仅支持String。

  1. public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) { 
  2.   switch (code) { 
  3.     case PLUS: { 
  4.       // 返回结果当前仅支持String,对于复杂结构可以序列化为ZSON字符串上报 
  5.       reply.writeString(ZSONObject.toZSONString(result)); 

虽然reply拥有writeInt,writeBoolean等等方法,但目前支持writeString。

2.被调用的java方法运行在非UI线程。

  1. public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) { 
  2.     LogUtil.info(TAG, "isMain:"+(EventRunner.current() == EventRunner.getMainEventRunner())); 

通过在onRemoteRequest处打印当前线程是否UI线程,我们可以得出js调用过来在java代码处是非UI线程的,如果在此处进行UI操作,就会有一定的风险。

如果存在UI操作,可以context.getUITaskDispatcher().syncDispatch(Runnable)或者new EventHandler(EventRunner.getMainEventRunner()).postTask方式处理。

  1. //方式1:uiTaskDispatcher,需要context 
  2. context.getUITaskDispatcher().syncDispatch(task); 
  3.  
  4. //方式2:EventHandler 
  5. new EventHandler(EventRunner.getMainEventRunner()).postTask(task); 

Parcel的大小是200kb,如果超过200kb(实际没这么多),会抛异常,如下:

官网对于Parcel的介绍

  • The default capacity of a Parcel instance is 200KB. If you want more or less, use setCapacity(int) to change it.
  • Note: Only data of the following data types can be written into or read from a Parcel: byte, byteArray, short, shortArray, int, intArray, long, longArray, float, floatArray, double, doubleArray, boolean, booleanArray, char, charArray, String, StringArray, PlainBooleanArray, Serializable, Sequenceable, and SequenceableArray.

解决办法:增大parcel的容量

  1. if (message.length() > 100 * 1000) { 
  2.     int capacity = (int) (message.length() * 2.1f); 
  3.     boolean setFlag = data.setCapacity(capacity); 
  4.     LogUtil.info(TAG, "SYNC capacity:" + capacity + " " + setFlag); 

4.CallAbility的TF_ASYNC 与Subscribe的区别

虽然两个看上去都像异步,但是从调用上看CallAbility的TF_ASYNC仍然是同步的,只是没有使用onRemoteRequest的reply方式进行回传。

  1. private boolean replyResult(MessageParcel reply, MessageOption optionint code, String data) { 
  2.     if (option.getFlags() == MessageOption.TF_SYNC) { 
  3.         // SYNC 
  4.         if (data.length() > 100 * 1000) { 
  5.             int capacity = (int) (data.length() * 2.1f); 
  6.             boolean setFlag = reply.setCapacity(capacity); 
  7.             LogUtil.info(TAG, "SYNC capacity:" + capacity + " " + setFlag); 
  8.         } 
  9.         reply.writeString(data); 
  10.     } else { 
  11.         // ASYNC 
  12.         MessageParcel responseData = MessageParcel.obtain(); 
  13.         if (data.length() > 100 * 1000) { 
  14.             int capacity = (int) (data.length() * 2.1f); 
  15.             boolean setFlag = responseData.setCapacity(capacity); 
  16.             LogUtil.info(TAG, "ASYNC capacity:" + capacity + " " + setFlag); 
  17.         } 
  18.         responseData.writeString(data); 
  19.         IRemoteObject remoteReply = reply.readRemoteObject(); 
  20.         try { 
  21.             remoteReply.sendRequest(code, responseData, MessageParcel.obtain(), new MessageOption()); 
  22.         } catch (RemoteException exception) { 
  23.             LogUtil.error(TAG, "RemoteException", exception); 
  24.             return false
  25.         } finally { 
  26.             responseData.reclaim(); 
  27.         } 
  28.     } 
  29.     return true
  30.      
  31.      
  32.   private void replySubscribeMsg(int code, String message) { 
  33.     MessageParcel data = MessageParcel.obtain(); 
  34.     MessageParcel reply = MessageParcel.obtain(); 
  35.     MessageOption option = new MessageOption(); 
  36.     if (message.length() > 100 * 1000) { 
  37.         int capacity = (int) (message.length() * 2.1f); 
  38.         boolean setFlag = data.setCapacity(capacity); 
  39.         LogUtil.info(TAG, "SYNC capacity:" + capacity + " " + setFlag); 
  40.     } 
  41.     data.writeString(message); 
  42.     // 如果仅支持单FA订阅,可直接触发回调:remoteObjectHandler.sendRequest(100, data, reply, option); 
  43.     try { 
  44.         remoteObject.sendRequest(code, data, reply, option); 
  45.     } catch (RemoteException e) { 
  46.         e.printStackTrace(); 
  47.     } 
  48.     reply.reclaim(); 
  49.     data.reclaim(); 

新方式一: js2java-codegen

从上面实现java,js通信的步骤可以看出,我们在java端对js传过来的方法、方法参数进行解析,解析完值后如果有返回值还需要通过Parcel回传。要是能够系统自动帮我们实现对方法名,方法参数的解析,那就省去一大部分工作。

正好,鸿蒙推出了js2java-codegen这个注解。

条件

Toolchains的2.2.0.3以上

实现步骤

1.在module下的gradle开启js2java-codegen。

  1. ohos { 
  2.     ... 
  3.     defaultConfig { 
  4.         .... 
  5.         javaCompileOptions { 
  6.             annotationProcessorOptions { 
  7.                 arguments = ['jsOutputDir': rootDir.path+'./entry/src/main/js/default/generated'] // 方式1设置生成路径 
  8.                 //arguments = ['jsOutputDir': project.file("src/main/js/default/generated")] //方式2设置路径 
  9.             } 
  10.         } 
  11.     } 
  12.     compileOptions { 
  13.         f2pautogenEnabled  true // 此处为启用js2java-codegen工具的开关 
  14.     } 

 注:jsOutputDir的路径一定要配置好,否则下面的编译阶段会报以下错误。

2.编写提供给js调用的java类。

  1. @InternalAbility(registerTo = "com.freesonwill.facallpa.MainAbility"
  2. public class JsBridgeX { 
  3.     private static final String TAG = "JsBridgeX"
  4.      
  5.     @ContextInject 
  6.     AbilityContext abilityContext; 
  7.  
  8.     public int add(int a, int b) { 
  9.         return a + b; 
  10.     } 

解释:

1)使用InternalAbility注解java类,注解处理器会根据此类生成对应JsBridgeXStub.java和JsBridgeX.js,这两个类帮我们建立了通信通道;

2)属性registerTo设为想要注册到的Ability类的全称。因为开发中我们可能用到context相关的方法,比如启动一个Ability。这里的registerTo和下面的@ContextInject配合使用,使被修饰的abilityContext指向MainAbility;

3.编译

点击Build -> Build HAP(s)/APP(s) -> Build HAP(s),js2java-codegen工具会为我们生成通信通道,JsBridgeXStub.java和JsBridgeX.js。

JsBridgeXStub.java

  1. public class JsBridgeXStub extends AceInternalAbility { 
  2.  public static final String BUNDLE_NAME = "com.freesonwill.facallpa"
  3.  
  4.  public static final String ABILITY_NAME = "com.freesonwill.facallpa.JsBridgeXStub"
  5. ... 
  6.  
  7.  private AbilityContext abilityContext; 
  8.  
  9.  public JsBridgeXStub() { 
  10.    super(BUNDLE_NAME, ABILITY_NAME); 
  11.  } 
  12.  
  13.  public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, 
  14.      MessageOption option) { 
  15.    Map<String, Object> result = new HashMap<String, Object>(); 
  16.    switch(code) { 
  17.      case OPCODE_add: { 
  18.      java.lang.String zsonStr = data.readString(); 
  19.      ZSONObject zsonObject = ZSONObject.stringToZSON(zsonStr); 
  20.      int a = zsonObject.getObject("a"int.class); //解析add方法第1个参数 
  21.      int b = zsonObject.getObject("b"int.class); //解析add方法第2个参数 
  22.      result.put("code", SUCCESS); 
  23.      result.put("abilityResult", service.add(a, b));//OPCODE_add 对应servcie.add方法 
  24.      break;} 

点开JsBridgeXStub.java,我们看到,在onRemoteRequest这里仍然是方法名,方法参数解析。

JsBridgeX.js

  1.  const BUNDLE_NAME = 'com.freesonwill.facallpa'
  2. const ABILITY_NAME = 'com.freesonwill.facallpa.JsBridgeXStub'
  3. ... 
  4. const sendRequest = async (opcode, data) => { 
  5.     var action = {}; 
  6.     action.bundleName = BUNDLE_NAME; 
  7.     action.abilityName = ABILITY_NAME; 
  8.     action.messageCode = opcode; 
  9.     action.data = data; 
  10.     action.abilityType = ABILITY_TYPE_INTERNAL; 
  11.     action.syncOption = ACTION_SYNC; 
  12.     return FeatureAbility.callAbility(action);  //仍然是熟悉的FeatureAbility.callAbility方法 
  13. class JsBridgeX { 
  14.     async add(a, b) { 
  15.         if (arguments.length != 2) { 
  16.             throw new Error("Method expected 2 arguments, got " + arguments.length); 
  17.         } 
  18.         let data = {}; 
  19.         data.a = a; 
  20.         data.b = b; 
  21.         const result = await sendRequest(OPCODE_add, data); 
  22.         return JSON.parse(result); 
  23.     } 

我们看到,这里使用了FeatureAbility.callAbility,action的data属性中包含了要调用的方法名(add)和方法参数(a,b)。

4.使用

  1. import JsBridgeX from '../../generated/JsBridgeX.js'
  2. const bridge = new JsBridgeX() 
  3.  
  4. ... 
  5. //使用方式1:promise+then 
  6. bridge.add(a, b).then(ret => { 
  7.     prompt.showToast({ 
  8.         message: `${a}+${b} = ${ret.abilityResult}` 
  9.     }) 
  10. }) 
  11. //使用方式2:await 
  12. var ret = await bridge.add(a, b); 
  13. prompt.showToast({ 
  14.     message: `${a}+${b} = ${ret.abilityResult}` 
  15. }) 

注意点

1.返回值从abilityResult属性获取,如上ret.abilityResult。

  1. prompt.showToast({ 
  2.     message: `${a}+${b} = ${ret.abilityResult}` 
  3. }) 

2.只支持同步方法FeatureAbility.callAbility,不支持FeatureAbility.subscribeAbilityEvent、FeatureAbility.unsubscribeAbilityEvent。从目前官网资料看,生成的方法都是FeatureAbility.callAbility方式。

3.void方法,private,protected,default访问权限的方法不会生成。

  1. public void add(int a, int b) {//add 方法不会生成 
  2.     return a + b; 
  3.  
  4. int add(int a, int b) {//private,protected,default的方法不会生成 
  5.     return a + b; 

4.生成对应的js方法都是async的。

  1. async add(a, b) { 
  2.     if (arguments.length != 2) { 
  3.         throw new Error("Method expected 2 arguments, got " + arguments.length); 
  4.     } 
  5.     let data = {}; 
  6.     data.a = a; 
  7.     data.b = b; 
  8.     const result = await sendRequest(OPCODE_add, data); 
  9.     return JSON.parse(result); 

 5.非public方法通过编译不会暴露给js,即生成的js代码没有对应方法,如果想要public方法也不想暴露给js用,可以使用**@ExportIgnore**。

  1. @ExportIgnore 
  2. public boolean helloChar(){ 
  3.     return true

 6.只支持文件中public的顶层类,不支持接口类和注解类。

7.跟传统的调用方式不同,js端必须new 一个实例,通过实例调用方法。

  1. const bridge = new JsBridgeX() 
  2. bridge.add(a, b) 

新方式二:LocalParticleAbility

不同于上面的js2java-codegen这种方式,LocalParticleAbility在系统内部已经建立好通道,不需要编译生成额外的代码,在java端实现,在js端调用就行,比js2java-codegen更加简单。

条件

从API Version 6 开始支持。

实现步骤

1.java端实现接口LocalParticleAbility,添加方法。

  1. package com.freesonwill.facallpa.biz; 
  2. public class MyLocalParticleAbility implements LocalParticleAbility { 
  3.     //interface 
  4.     public int add(int a, int b) { 
  5.         return a + b; 
  6.     } 
  7.  
  8.     public void addAsync(int a, int b, Callback cb) { 
  9.         new Thread(() -> { 
  10.             int ret = a + b;//假设这里是一个耗时操作 
  11.             cb.reply(ret ); 
  12.         }).start(); 
  13.     } 

 注:这里列举了同步和异步的两种方式,异步需要LocalParticleAbility.Callback的reply方法返回。

2.注册。

  1. public class MainAbility extends AceAbility { 
  2.  
  3.     @Override 
  4.     public void onStart(Intent intent) { 
  5.         super.onStart(intent); 
  6.         MyLocalParticleAbility.getInstance().register(this); 
  7.     } 
  8.     @Override 
  9.     public void onStop() { 
  10.         super.onStop(); 
  11.         MyLocalParticleAbility.getInstance().deregister(this); 
  12.     } 

 3.js端调用。

a) 创建LocalParticleAbility对象

  1. this.javaInterface = createLocalParticleAbility('com.freesonwill.facallpa.biz.MyLocalParticleAbility'); 

 b)调用方法

调用同步方法

  1. add(a, b) { 
  2.     this.javaInterface.add(a, b).then(ret => { 
  3.         prompt.showToast({ 
  4.             message: `${a}+${b} = ${ret}` 
  5.         }) 
  6.     }) 
  7. }, 
  8. 或者   
  9. async add(a, b) { 
  10.     let ret = await this.javaInterface.add(a, b); 
  11.     console.log("rst:" + JSON.stringify(ret)) 
  12. }, 

 调用异步方法

  1. addAsync(a, b) {  
  2.     this.javaInterface.addAsync(1, 2, rst => { 
  3.         console.log("rst:" + JSON.stringify(rst)) 
  4.     }) 
  5. }, 

从上面可以看出LocalParticleAbility既支持同步又支持异步,弥补了js2java-codegen的不足。

注意点

  1. 目前从官网中js端的createLocalParticleAbility找不到,目前调试不行。
  2. API Version 6 开始支持。
  3. 需要注册和反注册。

总结

传统JS FA的调用方式需要关注方法名,方法参数的传递解析,比较复杂,后续大概率会被LocalParticleAbility这种简洁方式替换。

js2java-codegen提供了APT技术帮我们生成通道代码,但是受限于不能实现异步,函数必须有返回值等,实用性不强。

LocalParticleAbility优势很大,是未来的方向,目前虽然提供了文档,但是在DevEco Studio 3.0端 SDK 6仍然不能成功。

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2021-09-10 15:13:41

鸿蒙HarmonyOS应用

2022-03-07 14:58:10

ArkUIJS FAJava

2021-11-15 15:11:14

鸿蒙HarmonyOS应用

2021-12-20 10:07:39

鸿蒙HarmonyOS应用

2022-02-17 21:28:08

AbilityJSFA鸿蒙

2021-07-16 09:54:49

鸿蒙HarmonyOS应用

2022-06-02 14:27:05

UI框架JS

2022-02-17 21:19:35

JSFA操作系统鸿蒙

2022-02-24 16:00:59

Ability鸿蒙JS

2022-05-24 15:06:57

AbilityeTS FA鸿蒙

2021-06-24 14:41:16

鸿蒙HarmonyOS应用

2022-05-20 10:56:54

AbilityeTS FA调用

2017-07-17 11:12:07

大数据医疗戴尔

2009-12-25 16:40:33

DDN接入

2009-04-25 09:30:55

Firefox浏览器

2018-09-11 11:03:45

存储新方式DNA存储

2013-08-15 09:54:02

2010-11-25 10:07:01

Ubuntu滚动更新

2011-09-07 14:30:35

MPLSMPLS故障

2022-02-17 21:05:26

AbilityJS FAJava PA
点赞
收藏

51CTO技术栈公众号