FormExtensionAbility(服務卡片)

卡片概述

FormExtensionAbility就是服務卡片擴展組件(以下簡稱“卡片”),是一種界面展示形式,可以將應用的重要信息或操作前置到卡片,以達到服務直達,減少體驗層級的目的。

卡片常用于嵌入到其他應用(當前只支持系統(tǒng)應用)中作為其界面的一部分顯示,并支持拉起頁面、發(fā)送消息等基礎的交互功能。

卡片的基本概念:

  • 卡片使用方:顯示卡片內容的宿主應用,控制卡片在宿主中展示的位置。

  • 卡片管理服務:用于管理系統(tǒng)中所添加卡片的常駐代理服務,包括卡片對象的管理與使用,以及卡片周期性刷新等。

  • 卡片提供方:提供卡片顯示內容原子化服務,控制卡片的顯示內容、控件布局以及控件點擊事件。

運作機制

卡片框架的運作機制如圖1所示。

圖1 卡片框架運作機制(Stage模型)

卡片使用方包含以下模塊:

  • 卡片使用:包含卡片的創(chuàng)建、刪除、請求更新等操作。
  • 通信適配層:由OpenHarmony SDK提供,負責與卡片管理服務通信,用于將卡片的相關操作到卡片管理服務。

卡片管理服務包含以下模塊:

  • 周期性刷新:在卡片添加后,根據卡片的刷新策略啟動定時任務周期性觸發(fā)卡片的刷新。
  • 卡片緩存管理:在卡片添加到卡片管理服務后,對卡片的視圖信息進行緩存,以便下次獲取卡片時可以直接返回緩存數據,降低時延。
  • 卡片生命周期管理:對于卡片切換到后臺或者被遮擋時,暫??ㄆ乃⑿拢灰约翱ㄆ纳?卸載場景下對卡片數據的更新和清理。
  • 卡片使用方對象管理:對卡片使用方的RPC對象進行管理,用于使用方請求進行校驗以及對卡片更新后的回調處理。
  • 通信適配層:負責與卡片使用方和提供方進行RPC通信。

卡片提供方包含以下模塊:

  • 卡片服務:由卡片提供方開發(fā)者實現,開發(fā)者實現生命周期處理創(chuàng)建卡片、更新卡片以及刪除卡片等請求,提供相應的卡片服務。
  • 卡片提供方實例管理模塊:由卡片提供方開發(fā)者實現,負責對卡片管理服務分配的卡片實例進行持久化管理。
  • 通信適配層:由OpenHarmony SDK提供,負責與卡片管理服務通信,用于將卡片的更新數據主動推送到卡片管理服務。

說明: 實際開發(fā)時只需要作為卡片提供方進行卡片內容的開發(fā),卡片使用方和卡片管理服務由系統(tǒng)自動處理。

接口說明

FormExtensionAbility類擁有如下API接口,具體的API介紹詳見接口文檔。

接口名 描述
onAddForm(want:?Want):?formBindingData.FormBindingData 卡片提供方接收創(chuàng)建卡片的通知接口。
onCastToNormalForm(formId:?string):?void 卡片提供方接收臨時卡片轉常態(tài)卡片的通知接口。
onUpdateForm(formId:?string):?void 卡片提供方接收更新卡片的通知接口。
onChangeFormVisibility(newStatus:?{?[key:?string]:?number?}):?void 卡片提供方接收修改可見性的通知接口。
onFormEvent(formId:?string,?message:?string):?void 卡片提供方接收處理卡片事件的通知接口。
onRemoveForm(formId:?string):?void 卡片提供方接收銷毀卡片的通知接口。
onConfigurationUpdate(config:?Configuration):?void 當系統(tǒng)配置更新時調用。
onShareForm?(formId:?string):?{?[key:?string]:?any?} 卡片提供方接收卡片分享的通知接口。

FormExtensionAbility類還擁有成員context,為FormExtensionContext類,具體的API介紹詳見接口文檔。

接口名 描述
startAbility(want:?Want,?callback:?AsyncCallback<void>):?void 回調形式拉起一個卡片所屬應用的UIAbility(系統(tǒng)接口,三方應用不支持調用,需申請后臺拉起權限)。
startAbility(want:?Want):?Promise<void> Promise形式拉起一個卡片所屬應用的UIAbility(系統(tǒng)接口,三方應用不支持調用,需申請后臺拉起權限)。

formProvider類有如下API接口,具體的API介紹詳見接口文檔

接口名 描述
setFormNextRefreshTime(formId:?string,?minute:?number,?callback:?AsyncCallback<void>):?void; 設置指定卡片的下一次更新時間。
setFormNextRefreshTime(formId:?string,?minute:?number):?Promise<void>; 設置指定卡片的下一次更新時間,以promise方式返回。
updateForm(formId:?string,?formBindingData:?FormBindingData,?callback:?AsyncCallback<void>):?void; 更新指定的卡片。
updateForm(formId:?string,?formBindingData:?FormBindingData):?Promise<void>; 更新指定的卡片,以promise方式返回。

formBindingData類有如下API接口,具體的API介紹詳見接口文檔。

接口名 描述
createFormBindingData(obj?:?Object? ?string):?FormBindingData 創(chuàng)建一個FormBindingData對象。

開發(fā)步驟

Stage卡片開發(fā),即基于Stage模型的卡片提供方開發(fā),主要涉及如下關鍵步驟:

  • 創(chuàng)建卡片FormExtensionAbility`:卡片生命周期回調函數FormExtensionAbility開發(fā)。

  • 配置卡片配置文件:配置應用配置文件module.json5和profile配置文件。

  • 卡片數據交互:對卡片信息進行持久化管理。

  • 卡片數據交互:通過updateForm更新卡片顯示的信息。

  • 開發(fā)卡片頁面:使用HML+CSS+JSON開發(fā)JS卡片頁面。

  • 開發(fā)卡片事件:為卡片添加router事件和message事件。

創(chuàng)建卡片FormExtensionAbility

創(chuàng)建Stage模型的卡片,需實現FormExtensionAbility生命周期接口。先參考 DevEco Studio服務卡片開發(fā)指南 生成服務卡片模板。

  1. 在EntryFormAbility.ts中,導入相關模塊。
   import FormExtension from '@ohos.app.form.FormExtensionAbility';
   import formBindingData from '@ohos.app.form.formBindingData';
   import formInfo from '@ohos.app.form.formInfo';
   import formProvider from '@ohos.app.form.formProvider';
   import dataStorage from '@ohos.data.storage';
  1. 在EntryFormAbility.ts中,實現FormExtension生命周期接口。
   export default class EntryFormAbility extends FormExtension {
       onAddForm(want) {
           console.info('[EntryFormAbility] onAddForm');
           // 使用方創(chuàng)建卡片時觸發(fā),提供方需要返回卡片數據綁定類
           let obj = {
               "title": "titleOnCreate",
               "detail": "detailOnCreate"
           };
           let formData = formBindingData.createFormBindingData(obj);
           return formData;
       }
       onCastToNormalForm(formId) {
           // 使用方將臨時卡片轉換為常態(tài)卡片觸發(fā),提供方需要做相應的處理
           console.info('[EntryFormAbility] onCastToNormalForm');
       }
       onUpdateForm(formId) {
           // 若卡片支持定時更新/定點更新/卡片使用方主動請求更新功能,則提供方需要重寫該方法以支持數據更新
           console.info('[EntryFormAbility] onUpdateForm');
           let obj = {
               "title": "titleOnUpdate",
               "detail": "detailOnUpdate"
           };
           let formData = formBindingData.createFormBindingData(obj);
           formProvider.updateForm(formId, formData).catch((error) => {
               console.info('[EntryFormAbility] updateForm, error:' + JSON.stringify(error));
           });
       }
       onChangeFormVisibility(newStatus) {
           // 使用方發(fā)起可見或者不可見通知觸發(fā),提供方需要做相應的處理,僅系統(tǒng)應用生效
           console.info('[EntryFormAbility] onChangeFormVisibility');
       }
       onFormEvent(formId, message) {
           // 若卡片支持觸發(fā)事件,則需要重寫該方法并實現對事件的觸發(fā)
           console.info('[EntryFormAbility] onFormEvent');
       }
       onRemoveForm(formId) {
           // 刪除卡片實例數據
           console.info('[EntryFormAbility] onRemoveForm');
       }
       onConfigurationUpdate(config) {
           console.info('[EntryFormAbility] nConfigurationUpdate, config:' + JSON.stringify(config));
       }
       onAcquireFormState(want) {
           return formInfo.FormState.READY;
       }
   }

說明: FormExtensionAbility不能常駐后臺,即在卡片生命周期回調函數中無法處理長時間的任務。

配置卡片配置文件

  1. 卡片需要在module.json5配置文件中的extensionAbilities標簽下,配置ExtensionAbility相關信息。FormExtensionAbility需要填寫metadata元信息標簽,其中鍵名稱為固定字符串”ohos.extension.form”,資源為卡片的具體配置信息的索引。 配置示例如下:
   {
     "module": {
       // ...
       "extensionAbilities": [
         {
           "name": "EntryFormAbility",
           "srcEntrance": "./ets/entryformability/EntryFormAbility.ts",
           "label": "$string:EntryFormAbility_label",
           "description": "$string:EntryFormAbility_desc",
           "type": "form",
           "metadata": [
             {
               "name": "ohos.extension.form",
               "resource": "$profile:form_config"
             }
           ]
         }
       ]
     }
   }
  1. 卡片的具體配置信息。在上述FormExtensionAbility的元信息(”metadata”配置項)中,可以指定卡片具體配置信息的資源索引。例如當resource指定為$profile:form_config時,會使用開發(fā)視圖的resources/base/profile/目錄下的form_config.json作為卡片profile配置文件。內部字段結構說明如下表所示。 表1 卡片profile配置文件
屬性名稱 含義 數據類型 是否可缺省
name 表示卡片的類名,字符串最大長度為127字節(jié)。 字符串
description 表示卡片的描述。取值可以是描述性內容,也可以是對描述性內容的資源索引,以支持多語言。字符串最大長度為255字節(jié)。 字符串 可缺省,缺省為空。
src 表示卡片對應的UI代碼的完整路徑。 字符串
window 用于定義與顯示窗口相關的配置。 對象 可缺省
isDefault 表示該卡片是否為默認卡片,每個Ability有且只有一個默認卡片。true:默認卡片。false:非默認卡片。 布爾值
colorMode 表示卡片的主題樣式,取值范圍如下:auto:自適應。dark:深色主題。light:淺色主題。 字符串 可缺省,缺省值為“auto”。
supportDimensions 表示卡片支持的外觀規(guī)格,取值范圍:1??2:表示1行2列的二宮格。2??2:表示2行2列的四宮格。2??4:表示2行4列的八宮格。4??4:表示4行4列的十六宮格。 字符串數組
defaultDimension 表示卡片的默認外觀規(guī)格,取值必須在該卡片supportDimensions配置的列表中。 字符串
updateEnabled 表示卡片是否支持周期性刷新,取值范圍:true:表示支持周期性刷新,可以在定時刷新(updateDuration)和定點刷新(scheduledUpdateTime)兩種方式任選其一,優(yōu)先選擇定時刷新。false:表示不支持周期性刷新。 布爾類型
scheduledUpdateTime 表示卡片的定點刷新的時刻,采用24小時制,精確到分鐘。updateDuration參數優(yōu)先級高于scheduledUpdateTime,兩者同時配置時,以updateDuration配置的刷新時間為準。 字符串 可缺省,缺省值為“0:0”。
updateDuration 表示卡片定時刷新的更新周期,單位為30分鐘,取值為自然數。當取值為0時,表示該參數不生效。當取值為正整數N時,表示刷新周期為30*N分鐘。updateDuration參數優(yōu)先級高于scheduledUpdateTime,兩者同時配置時,以updateDuration配置的刷新時間為準。 數值 可缺省,缺省值為“0”。
formConfigAbility 表示卡片的配置跳轉鏈接,采用URI格式。 字符串 可缺省,缺省值為空。
formVisibleNotify 標識是否允許卡片使用卡片可見性通知。 字符串 可缺省,缺省值為空。
metaData 表示卡片的自定義信息,包含customizeData數組標簽。 對象 可缺省,缺省值為空。

配置示例如下:

   {
     "forms": [
       {
         "name": "widget",
         "description": "This is a service widget.",
         "src": "./js/widget/pages/index/index",
         "window": {
           "designWidth": 720,
           "autoDesignWidth": true
         },
         "colorMode": "auto",
         "isDefault": true,
         "updateEnabled": true,
         "scheduledUpdateTime": "10:30",
         "updateDuration": 1,
         "defaultDimension": "2*2",
         "supportDimensions": [
           "2*2"
         ]
       }
     ]
   }

卡片信息的持久化

因大部分卡片提供方都不是常駐服務,只有在需要使用時才會被拉起獲取卡片信息,且卡片管理服務支持對卡片進行多實例管理,卡片ID對應實例ID,因此若卡片提供方支持對卡片數據進行配置,則需要對卡片的業(yè)務數據按照卡片ID進行持久化管理,以便在后續(xù)獲取、更新以及拉起時能獲取到正確的卡片業(yè)務數據。

const DATA_STORAGE_PATH = "/data/storage/el2/base/haps/form_store";
async function storeFormInfo(formId: string, formName: string, tempFlag: boolean) {
    // 此處僅對卡片ID:formId,卡片名:formName和是否為臨時卡片:tempFlag進行了持久化
    let formInfo = {
        "formName": formName,
        "tempFlag": tempFlag,
        "updateCount": 0
    };
    try {
        const storage = await dataStorage.getStorage(DATA_STORAGE_PATH);
        // put form info
        await storage.put(formId, JSON.stringify(formInfo));
        console.info(`[EntryFormAbility] storeFormInfo, put form info successfully, formId: ${formId}`);
        await storage.flush();
    } catch (err) {
        console.error(`[EntryFormAbility] failed to storeFormInfo, err: ${JSON.stringify(err)}`);
    }
}

export default class EntryFormAbility extends FormExtension {
    // ...
    onAddForm(want) {
        console.info('[EntryFormAbility] onAddForm');

        let formId = want.parameters["ohos.extra.param.key.form_identity"];
        let formName = want.parameters["ohos.extra.param.key.form_name"];
        let tempFlag = want.parameters["ohos.extra.param.key.form_temporary"];
        // 將創(chuàng)建的卡片信息持久化,以便在下次獲取/更新該卡片實例時進行使用
        // 此接口請根據實際情況實現,具體請參考:FormExtAbility Stage模型卡片實例
        storeFormInfo(formId, formName, tempFlag);

        let obj = {
            "title": "titleOnCreate",
            "detail": "detailOnCreate"
        };
        let formData = formBindingData.createFormBindingData(obj);
        return formData;
    }
}

且需要適配onRemoveForm卡片刪除通知接口,在其中實現卡片實例數據的刪除。

const DATA_STORAGE_PATH = "/data/storage/el2/base/haps/form_store";
async function deleteFormInfo(formId: string) {
    try {
        const storage = await dataStorage.getStorage(DATA_STORAGE_PATH);
        // del form info
        await storage.delete(formId);
        console.info(`[EntryFormAbility] deleteFormInfo, del form info successfully, formId: ${formId}`);
        await storage.flush();
    } catch (err) {
        console.error(`[EntryFormAbility] failed to deleteFormInfo, err: ${JSON.stringify(err)}`);
    }
}

// ...

export default class EntryFormAbility extends FormExtension {
    // ...
    onRemoveForm(formId) {
        console.info('[EntryFormAbility] onRemoveForm');
        // 刪除之前持久化的卡片實例數據
        // 此接口請根據實際情況實現,具體請參考:FormExtAbility Stage模型卡片實例
        deleteFormInfo(formId);
    }
}

具體的持久化方法可以參考輕量級數據存儲開發(fā)指導。

需要注意的是,卡片使用方在請求卡片時傳遞給提供方應用的Want數據中存在臨時標記字段,表示此次請求的卡片是否為臨時卡片:

  • 常態(tài)卡片:卡片使用方會持久化的卡片;
  • 臨時卡片:卡片使用方不會持久化的卡片;

由于臨時卡片的數據具有非持久化的特殊性,某些場景例如卡片服務框架死亡重啟,此時臨時卡片數據在卡片管理服務中已經刪除,且對應的卡片ID不會通知到提供方,所以卡片提供方需要自己負責清理長時間未刪除的臨時卡片數據。同時對應的卡片使用方可能會將之前請求的臨時卡片轉換為常態(tài)卡片。如果轉換成功,卡片提供方也需要對對應的臨時卡片ID進行處理,把卡片提供方記錄的臨時卡片數據轉換為常態(tài)卡片數據,防止提供方在清理長時間未刪除的臨時卡片時,把已經轉換為常態(tài)卡片的臨時卡片信息刪除,導致卡片信息丟失。

卡片數據交互

當卡片應用需要更新數據時(如觸發(fā)了定時更新或定點更新),卡片應用獲取最新數據,并調用updateForm()接口主動觸發(fā)卡片的更新。

onUpdateForm(formId) {
    // 若卡片支持定時更新/定點更新/卡片使用方主動請求更新功能,則提供方需要重寫該方法以支持數據更新
    console.info('[EntryFormAbility] onUpdateForm');
    let obj = {
        "title": "titleOnUpdate",
        "detail": "detailOnUpdate"
    };
    let formData = formBindingData.createFormBindingData(obj);
    // 調用updateForm接口去更新對應的卡片,僅更新入參中攜帶的數據信息,其他信息保持不變
    formProvider.updateForm(formId, formData).catch((error) => {
        console.info('[EntryFormAbility] updateForm, error:' + JSON.stringify(error));
    });
}

開發(fā)卡片頁面

開發(fā)者可以使用類Web范式(HML+CSS+JSON)開發(fā)JS卡片頁面。生成如下卡片頁面,可以這樣配置卡片頁面文件:

說明: 當前僅支持JS擴展的類Web開發(fā)范式來實現卡片的UI界面。

  • HML:使用類Web范式的組件描述卡片的頁面信息。
  <div class="container">
    <stack>
      <div class="container-img">
        <image src="/common/widget.png" class="bg-img"></image>
      </div>
      <div class="container-inner">
        <text class="title">{{title}}</text>
        <text class="detail_text" onclick="routerEvent">{{detail}}</text>
      </div>
    </stack>
  </div>

  • CSS:HML中類Web范式組件的樣式信息。
  .container {
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }

  .bg-img {
    flex-shrink: 0;
    height: 100%;
  }

  .container-inner {
    flex-direction: column;
    justify-content: flex-end;
    align-items: flex-start;
    height: 100%;
    width: 100%;
    padding: 12px;
  }

  .title {
    font-size: 19px;
    font-weight: bold;
    color: white;
    text-overflow: ellipsis;
    max-lines: 1;
  }

  .detail_text {
    font-size: 16px;
    color: white;
    opacity: 0.66;
    text-overflow: ellipsis;
    max-lines: 1;
    margin-top: 6px;
  }

  • JSON:卡片頁面中的數據和事件交互。
  {
    "data": {
      "title": "TitleDefault",
      "detail": "TextDefault"
    },
    "actions": {
      "routerEvent": {
        "action": "router",
        "abilityName": "EntryAbility",
        "params": {
          "message": "add detail"
        }
      }
    }
  }

開發(fā)卡片事件

卡片支持為組件設置交互事件(action),包括router事件和message事件,其中router事件用于Ability跳轉,message事件用于卡片開發(fā)人員自定義點擊事件。

關鍵步驟說明如下:

  1. 在HML中為組件設置onclick屬性,其值對應到JSON文件的actions字段中。

  2. 設置router事件:

    • action屬性值為”router”。
    • abilityName為跳轉目標的Ability名(支持跳轉FA模型的PageAbility組件和Stage模型的UIAbility組件),如目前DevEco Studio創(chuàng)建的Stage模型的UIAbility默認名為EntryAbility。
    • params為傳遞給跳轉目標Ability的自定義參數,可以按需填寫。其值可以在目標Ability啟動時的want中的parameters里獲取。如Stage模型MainAbility的onCreate生命周期里的入參want的parameters字段下獲取到配置的參數。
  3. 設置message事件:

    • action屬性值為”message”。
    • params為message事件的用戶自定義參數,可以按需填寫。其值可以在卡片生命周期函數onFormEvent()中的message里獲取。

示例如下。

  • HML文件
  <div class="container">
    <stack>
      <div class="container-img">
        <image src="/common/widget.png" class="bg-img"></image>
      </div>
      <div class="container-inner">
        <text class="title" onclick="routerEvent">{{title}}</text>
        <text class="detail_text" onclick="messageEvent">{{detail}}</text>
      </div>
    </stack>
  </div>

  • CSS文件
  .container {
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }

  .bg-img {
    flex-shrink: 0;
    height: 100%;
  }

  .container-inner {
    flex-direction: column;
    justify-content: flex-end;
    align-items: flex-start;
    height: 100%;
    width: 100%;
    padding: 12px;
  }

  .title {
    font-size: 19px;
    font-weight: bold;
    color: white;
    text-overflow: ellipsis;
    max-lines: 1;
  }

  .detail_text {
    font-size: 16px;
    color: white;
    opacity: 0.66;
    text-overflow: ellipsis;
    max-lines: 1;
    margin-top: 6px;
  }

  • JSON文件
  {
    "data": {
      "title": "TitleDefault",
      "detail": "TextDefault"
    },
    "actions": {
      "routerEvent": {
        "action": "router",
        "abilityName": "EntryAbility",
        "params": {
          "info": "router info",
          "message": "router message"
        }
      },
      "messageEvent": {
        "action": "message",
        "params": {
          "detail": "message detail"
        }
      }
    }
  }

  • 在UIAbility中接收router事件并獲取參數
  import UIAbility from '@ohos.app.ability.UIAbility'

  export default class EntryAbility extends UIAbility {
      onCreate(want, launchParam) {
          // 獲取router事件中傳遞的info參數
          if (want.parameters.info === "router info") {
              // do something
              // console.log("router info:" + want.parameters.info)
          }
          // 獲取router事件中傳遞的message參數
          if (want.parameters.message === "router message") {
              // do something
              // console.log("router message:" + want.parameters.message)
          }
      }
      // ...
  };

  • 在FormExtensionAbility中接收message事件并獲取參數
  import FormExtension from '@ohos.app.form.FormExtensionAbility';

  export default class FormAbility extends FormExtension {
      // ...
      onFormEvent(formId, message) {
          // 獲取message事件中傳遞的detail參數
          let msg = JSON.parse(message)
          if (msg.params.detail === "message detail") {
              // do something
              // console.log("message info:" + msg.params.detail)
          }
      }
      // ...
  };

限制

為了降低FormExtensionAbility能力被三方應用濫用的風險,在FormExtensionAbility中限制以下接口的調用

  • @ohos.ability.particleAbility.d.ts
  • @ohos.backgroundTaskManager.d.ts
  • @ohos.resourceschedule.backgroundTaskManager.d.ts
  • @ohos.multimedia.camera.d.ts
  • @ohos.multimedia.audio.d.ts
  • @ohos.multimedia.media.d.ts
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容