亚洲精品亚洲人成在线观看麻豆,在线欧美视频一区,亚洲国产精品一区二区动图,色综合久久丁香婷婷

              當(dāng)前位置:首頁 > IT技術(shù) > Web編程 > 正文

              HarmonyOS JS FA調(diào)用PA新方式
              2021-10-28 15:26:37

              作者:包月東

              簡介

              ? JS FA調(diào)用Java PA一般通過FeatureAbility這種傳統(tǒng)方式調(diào)用。本文先回顧傳統(tǒng)方式,在傳統(tǒng)方式的痛點上引入兩種新方式。

              傳統(tǒng)方式

              方法說明

              常見的JS FA調(diào)用Java Pa一般通過以下三個方法

              (1) FeatureAbility.callAbility(OBJECT):調(diào)用PA能力。
              (2) FeatureAbility.subscribeAbilityEvent(OBJECT, Function):訂閱PA能力。
              (3) FeatureAbility.unsubscribeAbilityEvent(OBJECT):取消訂閱PA能力。

              1.能常用于同步調(diào)用方法

              比如:

              //實現(xiàn)(Java)
              public int add(int a, int b) {
                  return a + b;
              }
              
              //調(diào)用:
              add: async function (data) {
                  let params = getRequestAction(ACTION_MESSAGE_CODE_ADD);
                  params.data = data;
                  var ret = await FeatureAbility.callAbility(params)
                  console.log("result:" + JSON.parse(ret) + "," + ret)
                  return JSON.parse(ret)
              }

              2.常用于調(diào)用異步方法,因為(2)是通過監(jiān)聽的方式實現(xiàn)異步回調(diào),所以在不需要監(jiān)聽時最好調(diào)用(3)取消

              比如:

              //實現(xiàn)(Java)
              public void addAsync(int a, int b, Callback cb) {
                  new Thread(() -> {
                      int ret = a + b;//假設(shè)這里是一個耗時操作
                      cb.reply(ret );
                  }).start();
              }
              
              //調(diào)用(JS):
              addAsync: async function (data,func) {
                  let params = getRequestAction(ACTION_MESSAGE_CODE_ADDAsync);
                  params.data = data;
                  FeatureAbility.subscribeAbilityEvent(params, (callbackData) => {
                      var callbackJson = JSON.parse(callbackData);
                      //console.log('getAirlineOpenCity is: ' + JSON.stringify(callbackJson.data));
                      func(callbackJson.data)
                  })
              },

              實現(xiàn)步驟

              在開發(fā)過程中應(yīng)用中常用Internal Ability,現(xiàn)以Internal Ability說明。

              Internal Ability需要實現(xiàn)的步驟:

              (1)在java中創(chuàng)建java類繼承AceInternalAbility,設(shè)置setInternalAbilityHandler回調(diào),在回調(diào)方法onRemoteRequest中通過命令碼code匹配對應(yīng)的方法名,Parcel中解析方法參數(shù);

              (2)在AbilityPackage或者Ability的生命周期對java類對象進(jìn)行注冊和反注冊;

              (3)在JS中創(chuàng)建對應(yīng)JS類,調(diào)用FeatureAbility.callAbility,F(xiàn)eatureAbility.subscribeAbilityEvent傳遞命令碼和方法參數(shù);

              痛點

              從實現(xiàn)步驟看出,建立Js FA到Java PA的通道,比較繁瑣,以下操作如果能夠由系統(tǒng)自動完成,那么用戶只關(guān)注java端方法實現(xiàn)、js端調(diào)用,使用就簡便很多。

              (1)進(jìn)行注冊,反注冊;

              (2)解析方法名,方法參數(shù);

              (3)返回值需要通過parcel進(jìn)行回傳;

              注意點

              1. java給js端返回結(jié)果(回消息)時,僅支持String。

                public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
                 switch (code) {
                   case PLUS: {
                     // 返回結(jié)果當(dāng)前僅支持String,對于復(fù)雜結(jié)構(gòu)可以序列化為ZSON字符串上報
                     reply.writeString(ZSONObject.toZSONString(result));

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

              2. 被調(diào)用的java方法運行在非UI線程。

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

                通過在onRemoteRequest處打印當(dāng)前線程是否UI線程,我們可以得出js調(diào)用過來在java代碼處是非UI線程的,如果在此處進(jìn)行UI操作,就會有一定的風(fēng)險。

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

                //方式1:uiTaskDispatcher,需要context
                context.getUITaskDispatcher().syncDispatch(task);
                
                //方式2:EventHandler
                new EventHandler(EventRunner.getMainEventRunner()).postTask(task);
              3. Parcel的大小是200kb,如果超過200kb(實際沒這么多),會拋異常,如下:

                image-20211021192619588

                官網(wǎng)對于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的容量

                if (message.length() > 100 * 1000) {
                   int capacity = (int) (message.length() * 2.1f);
                   boolean setFlag = data.setCapacity(capacity);
                   LogUtil.info(TAG, "SYNC capacity:" + capacity + " " + setFlag);
                }
              4. CallAbility的TF_ASYNC 與Subscribe的區(qū)別
                雖然兩個看上去都像異步,但是從調(diào)用上看CallAbility的TF_ASYNC仍然是同步的,只是沒有使用onRemoteRequest的reply方式進(jìn)行回傳。

                private boolean replyResult(MessageParcel reply, MessageOption option, int code, String data) {
                   if (option.getFlags() == MessageOption.TF_SYNC) {
                       // SYNC
                       if (data.length() > 100 * 1000) {
                           int capacity = (int) (data.length() * 2.1f);
                           boolean setFlag = reply.setCapacity(capacity);
                           LogUtil.info(TAG, "SYNC capacity:" + capacity + " " + setFlag);
                       }
                       reply.writeString(data);
                   } else {
                       // ASYNC
                       MessageParcel responseData = MessageParcel.obtain();
                       if (data.length() > 100 * 1000) {
                           int capacity = (int) (data.length() * 2.1f);
                           boolean setFlag = responseData.setCapacity(capacity);
                           LogUtil.info(TAG, "ASYNC capacity:" + capacity + " " + setFlag);
                       }
                       responseData.writeString(data);
                       IRemoteObject remoteReply = reply.readRemoteObject();
                       try {
                           remoteReply.sendRequest(code, responseData, MessageParcel.obtain(), new MessageOption());
                       } catch (RemoteException exception) {
                           LogUtil.error(TAG, "RemoteException", exception);
                           return false;
                       } finally {
                           responseData.reclaim();
                       }
                   }
                   return true;
                }
                
                 private void replySubscribeMsg(int code, String message) {
                   MessageParcel data = MessageParcel.obtain();
                   MessageParcel reply = MessageParcel.obtain();
                   MessageOption option = new MessageOption();
                   if (message.length() > 100 * 1000) {
                       int capacity = (int) (message.length() * 2.1f);
                       boolean setFlag = data.setCapacity(capacity);
                       LogUtil.info(TAG, "SYNC capacity:" + capacity + " " + setFlag);
                   }
                   data.writeString(message);
                   // 如果僅支持單FA訂閱,可直接觸發(fā)回調(diào):remoteObjectHandler.sendRequest(100, data, reply, option);
                   try {
                       remoteObject.sendRequest(code, data, reply, option);
                   } catch (RemoteException e) {
                       e.printStackTrace();
                   }
                   reply.reclaim();
                   data.reclaim();
                }

              新方式一: js2java-codegen

              從上面實現(xiàn)java,js通信的步驟可以看出,我們在java端對js傳過來的方法、方法參數(shù)進(jìn)行解析,解析完值后如果有返回值還需要通過Parcel回傳。要是能夠系統(tǒng)自動幫我們實現(xiàn)對方法名,方法參數(shù)的解析,那就省去一大部分工作。

              正好,鴻蒙推出了js2java-codegen這個注解。

              條件

              Toolchains的2.2.0.3以上

              image-20211013114722216

              實現(xiàn)步驟

              1. 在module下的gradle開啟js2java-codegen。

                ohos {
                ...
                   defaultConfig {
                    ....
                       javaCompileOptions {
                           annotationProcessorOptions {
                               arguments = ['jsOutputDir': rootDir.path+'./entry/src/main/js/default/generated'] // 方式1設(shè)置生成路徑
                               //arguments = ['jsOutputDir': project.file("src/main/js/default/generated")] //方式2設(shè)置路徑
                           }
                       }
                   }
                   compileOptions {
                       f2pautogenEnabled  true // 此處為啟用js2java-codegen工具的開關(guān)
                   }

                注:jsOutputDir的路徑一定要配置好,否則下面的編譯階段會報以下錯誤。

                image-20210923190722760

              2. 編寫提供給js調(diào)用的java類。

                @InternalAbility(registerTo = "com.freesonwill.facallpa.MainAbility")
                public class JsBridgeX {
                   private static final String TAG = "JsBridgeX";
                
                   @ContextInject
                   AbilityContext abilityContext;
                
                   public int add(int a, int b) {
                       return a + b;
                   }
                

                解釋:

                1)使用InternalAbility注解java類,注解處理器會根據(jù)此類生成對應(yīng)JsBridgeXStub.java和JsBridgeX.js,這兩個類幫我們建立了通信通道;

                2)屬性registerTo設(shè)為想要注冊到的Ability類的全稱。因為開發(fā)中我們可能用到context相關(guān)的方法,比如啟動一個Ability。這里的registerTo和下面的@ContextInject配合使用,使被修飾的abilityContext指向MainAbility;

              3. 編譯

                點擊Build -> Build HAP(s)/APP(s) -> Build HAP(s),js2java-codegen工具會為我們生成通信通道,JsBridgeXStub.java和JsBridgeX.js。

                image-20211022104654564

                • JsBridgeXStub.java

                  public class JsBridgeXStub extends AceInternalAbility {
                  public static final String BUNDLE_NAME = "com.freesonwill.facallpa";
                  
                  public static final String ABILITY_NAME = "com.freesonwill.facallpa.JsBridgeXStub";
                  ...
                  
                  private AbilityContext abilityContext;
                  
                  public JsBridgeXStub() {
                   super(BUNDLE_NAME, ABILITY_NAME);
                  }
                  
                  public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply,
                     MessageOption option) {
                   Map<String, Object> result = new HashMap<String, Object>();
                   switch(code) {
                     case OPCODE_add: {
                     java.lang.String zsonStr = data.readString();
                     ZSONObject zsonObject = ZSONObject.stringToZSON(zsonStr);
                     int a = zsonObject.getObject("a", int.class); //解析add方法第1個參數(shù)
                     int b = zsonObject.getObject("b", int.class); //解析add方法第2個參數(shù)
                     result.put("code", SUCCESS);
                     result.put("abilityResult", service.add(a, b));//OPCODE_add 對應(yīng)servcie.add方法
                     break;}

                  點開JsBridgeXStub.java,我們看到,在onRemoteRequest這里仍然是方法名,方法參數(shù)解析。

                • JsBridgeX.js

                  const BUNDLE_NAME = 'com.freesonwill.facallpa';
                  const ABILITY_NAME = 'com.freesonwill.facallpa.JsBridgeXStub';
                  ...
                  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); //仍然是熟悉的FeatureAbility.callAbility方法
                  }
                  class JsBridgeX {
                  async add(a, b) {
                      if (arguments.length != 2) {
                          throw new Error("Method expected 2 arguments, got " + arguments.length);
                      }
                      let data = {};
                      data.a = a;
                      data.b = b;
                      const result = await sendRequest(OPCODE_add, data);
                      return JSON.parse(result);
                  }

                  我們看到,這里使用了FeatureAbility.callAbility,action的data屬性中包含了要調(diào)用的方法名(add)和方法參數(shù)(a,b)。

              4. 使用

                import JsBridgeX from '../../generated/JsBridgeX.js';
                const bridge = new JsBridgeX()
                
                ...
                //使用方式1:promise+then
                bridge.add(a, b).then(ret => {
                   prompt.showToast({
                       message: `${a}+$ = ${ret.abilityResult}`
                   })
                })
                //使用方式2:await
                var ret = await bridge.add(a, b);
                prompt.showToast({
                   message: `${a}+$ = ${ret.abilityResult}`
                })

              注意點

              1. 返回值從abilityResult屬性獲取,如上ret.abilityResult。

                prompt.showToast({
                   message: `${a}+$ = ${ret.abilityResult}`
                })
              2. 只支持同步方法FeatureAbility.callAbility,不支持FeatureAbility.subscribeAbilityEvent、FeatureAbility.unsubscribeAbilityEvent。從目前官網(wǎng)資料看,生成的方法都是FeatureAbility.callAbility方式。

              3. void方法,private,protected,default訪問權(quán)限的方法不會生成。

                public void add(int a, int b) {//add 方法不會生成
                    return a + b;
                }
                
                int add(int a, int b) {//private,protected,default的方法不會生成
                   return a + b;
                }
              4. 生成對應(yīng)的js方法都是async的。

                async add(a, b) {
                   if (arguments.length != 2) {
                       throw new Error("Method expected 2 arguments, got " + arguments.length);
                   }
                   let data = {};
                   data.a = a;
                   data.b = b;
                   const result = await sendRequest(OPCODE_add, data);
                   return JSON.parse(result);
                }
              5. 非public方法通過編譯不會暴露給js,即生成的js代碼沒有對應(yīng)方法,如果想要public方法也不想暴露給js用,可以使用@ExportIgnore。

                @ExportIgnore
                public boolean helloChar(){
                   return true;
                }
              6. 只支持文件中public的頂層類,不支持接口類和注解類。

              7. 跟傳統(tǒng)的調(diào)用方式不同,js端必須new 一個實例,通過實例調(diào)用方法。

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

              新方式二:LocalParticleAbility

              不同于上面的js2java-codegen這種方式,LocalParticleAbility在系統(tǒng)內(nèi)部已經(jīng)建立好通道,不需要編譯生成額外的代碼,在java端實現(xiàn),在js端調(diào)用就行,比js2java-codegen更加簡單。

              條件

              從API Version 6 開始支持。

              實現(xiàn)步驟

              1. java端實現(xiàn)接口LocalParticleAbility,添加方法。

                package com.freesonwill.facallpa.biz;
                public class MyLocalParticleAbility implements LocalParticleAbility {
                   //interface
                   public int add(int a, int b) {
                       return a + b;
                   }
                
                   public void addAsync(int a, int b, Callback cb) {
                       new Thread(() -> {
                           int ret = a + b;//假設(shè)這里是一個耗時操作
                           cb.reply(ret );
                       }).start();
                   }

                注:這里列舉了同步和異步的兩種方式,異步需要LocalParticleAbility.Callback的reply方法返回。

              2. 注冊。

                public class MainAbility extends AceAbility {
                
                   @Override
                   public void onStart(Intent intent) {
                       super.onStart(intent);
                       MyLocalParticleAbility.getInstance().register(this);
                   }
                   @Override
                   public void onStop() {
                       super.onStop();
                       MyLocalParticleAbility.getInstance().deregister(this);
                   }
                }
                
              3. js端調(diào)用。

                a) 創(chuàng)建LocalParticleAbility對象

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

                b)調(diào)用方法

                • 調(diào)用同步方法
                add(a, b) {
                   this.javaInterface.add(a, b).then(ret => {
                       prompt.showToast({
                           message: `${a}+$ = ${ret}`
                       })
                   })
                },
                或者  
                async add(a, b) {
                   let ret = await this.javaInterface.add(a, b);
                   console.log("rst:" + JSON.stringify(ret))
                },
                • 調(diào)用異步方法
                addAsync(a, b) { 
                   this.javaInterface.addAsync(1, 2, rst => {
                       console.log("rst:" + JSON.stringify(rst))
                   })
                },

                從上面可以看出LocalParticleAbility既支持同步又支持異步,彌補(bǔ)了js2java-codegen的不足。

              注意點

              1. 目前從官網(wǎng)中js端的createLocalParticleAbility找不到,目前調(diào)試不行。
              2. API Version 6 開始支持。
              3. 需要注冊和反注冊。

              總結(jié)

              1. 傳統(tǒng)JS FA的調(diào)用方式需要關(guān)注方法名,方法參數(shù)的傳遞解析,比較復(fù)雜,后續(xù)大概率會被LocalParticleAbility這種簡潔方式替換。
              2. js2java-codegen提供了APT技術(shù)幫我們生成通道代碼,但是受限于不能實現(xiàn)異步,函數(shù)必須有返回值等,實用性不強(qiáng)。
              3. LocalParticleAbility優(yōu)勢很大,是未來的方向,目前雖然提供了文檔,但是在DevEco Studio 3.0端 SDK 6仍然不能成功。

              項目地址

              https://gitee.com/freebeing/facallpa.git

              參考

              https://developer.harmonyos.com/cn/docs/documentation/doc-references/parcel-0000001054519018

              https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-js2java-codegen-0000001171039385

              https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/js2java-codegen

              https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-apis-localparticleability-overview-0000001064156060

              更多原創(chuàng)內(nèi)容請關(guān)注:開鴻 HarmonyOS 學(xué)院

              入門到精通、技巧到案例,系統(tǒng)化分享HarmonyOS開發(fā)技術(shù),共建鴻蒙生態(tài),歡迎投稿和訂閱,讓我們一起攜手前行共建鴻蒙生態(tài)。

              【本文正在參與51CTO HarmonyOS技術(shù)社區(qū)創(chuàng)作者激勵-星光計劃1.0】

              想了解更多關(guān)于鴻蒙的內(nèi)容,請訪問:

              51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

              https://harmonyos.51cto.com/#bkwz

              ::: hljs-center

              21_9.jpg

              :::

              本文摘自 :https://blog.51cto.com/h

              開通會員,享受整站包年服務(wù)立即開通 >