• 當前位置:首頁 > IT技術 > Web編程 > 正文

    HarmonyOS JS FA調用PA新方式
    2021-10-28 15:26:37

    作者:包月東

    簡介

    ? 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.能常用于同步調用方法

    比如:

    //實現(Java)
    public int add(int a, int b) {
        return a + b;
    }
    
    //調用:
    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.常用于調用異步方法,因為(2)是通過監聽的方式實現異步回調,所以在不需要監聽時最好調用(3)取消

    比如:

    //實現(Java)
    public void addAsync(int a, int b, Callback cb) {
        new Thread(() -> {
            int ret = a + b;//假設這里是一個耗時操作
            cb.reply(ret );
        }).start();
    }
    
    //調用(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)
        })
    },

    實現步驟

    在開發過程中應用中常用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。

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

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

    2. 被調用的java方法運行在非UI線程。

      public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
         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:uiTaskDispatcher,需要context
      context.getUITaskDispatcher().syncDispatch(task);
      
      //方式2:EventHandler
      new EventHandler(EventRunner.getMainEventRunner()).postTask(task);
    3. Parcel的大小是200kb,如果超過200kb(實際沒這么多),會拋異常,如下:

      image-20211021192619588

      官網對于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的區別
      雖然兩個看上去都像異步,但是從調用上看CallAbility的TF_ASYNC仍然是同步的,只是沒有使用onRemoteRequest的reply方式進行回傳。

      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訂閱,可直接觸發回調:remoteObjectHandler.sendRequest(100, data, reply, option);
         try {
             remoteObject.sendRequest(code, data, reply, option);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
         reply.reclaim();
         data.reclaim();
      }

    新方式一: js2java-codegen

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

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

    條件

    Toolchains的2.2.0.3以上

    image-20211013114722216

    實現步驟

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

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

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

      image-20210923190722760

    2. 編寫提供給js調用的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類,注解處理器會根據此類生成對應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。

      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個參數
           int b = zsonObject.getObject("b", int.class); //解析add方法第2個參數
           result.put("code", SUCCESS);
           result.put("abilityResult", service.add(a, b));//OPCODE_add 對應servcie.add方法
           break;}

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

      • 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屬性中包含了要調用的方法名(add)和方法參數(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。從目前官網資料看,生成的方法都是FeatureAbility.callAbility方式。

    3. void方法,private,protected,default訪問權限的方法不會生成。

      public void add(int a, int b) {//add 方法不會生成
          return a + b;
      }
      
      int add(int a, int b) {//private,protected,default的方法不會生成
         return a + b;
      }
    4. 生成對應的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代碼沒有對應方法,如果想要public方法也不想暴露給js用,可以使用@ExportIgnore。

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

    7. 跟傳統的調用方式不同,js端必須new 一個實例,通過實例調用方法。

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

    新方式二:LocalParticleAbility

    不同于上面的js2java-codegen這種方式,LocalParticleAbility在系統內部已經建立好通道,不需要編譯生成額外的代碼,在java端實現,在js端調用就行,比js2java-codegen更加簡單。

    條件

    從API Version 6 開始支持。

    實現步驟

    1. java端實現接口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;//假設這里是一個耗時操作
                 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端調用。

      a) 創建LocalParticleAbility對象

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

      b)調用方法

      • 調用同步方法
      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))
      },
      • 調用異步方法
      addAsync(a, b) { 
         this.javaInterface.addAsync(1, 2, rst => {
             console.log("rst:" + JSON.stringify(rst))
         })
      },

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

    注意點

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

    總結

    1. 傳統JS FA的調用方式需要關注方法名,方法參數的傳遞解析,比較復雜,后續大概率會被LocalParticleAbility這種簡潔方式替換。
    2. js2java-codegen提供了APT技術幫我們生成通道代碼,但是受限于不能實現異步,函數必須有返回值等,實用性不強。
    3. LocalParticleAbility優勢很大,是未來的方向,目前雖然提供了文檔,但是在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

    更多原創內容請關注:開鴻 HarmonyOS 學院

    入門到精通、技巧到案例,系統化分享HarmonyOS開發技術,共建鴻蒙生態,歡迎投稿和訂閱,讓我們一起攜手前行共建鴻蒙生態。

    【本文正在參與51CTO HarmonyOS技術社區創作者激勵-星光計劃1.0】

    想了解更多關于鴻蒙的內容,請訪問:

    51CTO和華為官方合作共建的鴻蒙技術社區

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

    ::: hljs-center

    21_9.jpg

    :::

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

    開通會員,享受整站包年服務
    国产呦精品一区二区三区网站|久久www免费人咸|精品无码人妻一区二区|久99久热只有精品国产15|中文字幕亚洲无线码