卡片概述
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ā)指南 生成服務卡片模板。
- 在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';
- 在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不能常駐后臺,即在卡片生命周期回調函數中無法處理長時間的任務。
配置卡片配置文件
- 卡片需要在
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"
}
]
}
]
}
}
- 卡片的具體配置信息。在上述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ā)人員自定義點擊事件。
關鍵步驟說明如下:
在HML中為組件設置onclick屬性,其值對應到JSON文件的actions字段中。
-
設置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字段下獲取到配置的參數。
-
設置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