• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 服务卡片开发指导
2
3
4## 卡片概述
5
6服务卡片(以下简称“卡片”)是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达、减少体验层级的目的。
7
8卡片常用于嵌入到其他应用(当前只支持系统应用)中作为其界面的一部分显示,并支持拉起页面、发送消息等基础的交互功能。
9
10卡片的基本概念:
11
12- 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。
13
14- 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。
15
16- 卡片提供方:提供卡片显示内容原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。
17
18
19## 运作机制
20
21卡片框架的运作机制如图1所示。
22
23  **图1** 卡片框架运作机制(FA模型)
24![form-extension](figures/form-extension.png)
25
26卡片使用方包含以下模块:
27
28- 卡片使用:包含卡片的创建、删除、请求更新等操作。
29
30- 通信适配层:由OpenHarmony SDK提供,负责与卡片管理服务通信,用于将卡片的相关操作到卡片管理服务。
31
32卡片管理服务包含以下模块:
33
34- 周期性刷新:在卡片添加后,根据卡片的刷新策略启动定时任务周期性触发卡片的刷新。
35
36- 卡片缓存管理:在卡片添加到卡片管理服务后,对卡片的视图信息进行缓存,以便下次获取卡片时可以直接返回缓存数据,降低时延。
37
38- 卡片生命周期管理:对于卡片切换到后台或者被遮挡时,暂停卡片的刷新;以及卡片的升级/卸载场景下对卡片数据的更新和清理。
39
40- 卡片使用方对象管理:对卡片使用方的RPC对象进行管理,用于使用方请求进行校验以及对卡片更新后的回调处理。
41
42- 通信适配层:负责与卡片使用方和提供方进行RPC通信。
43
44卡片提供方包含以下模块:
45
46- 卡片服务:由卡片提供方开发者实现,开发者实现生命周期处理创建卡片、更新卡片以及删除卡片等请求,提供相应的卡片服务。
47
48- 卡片提供方实例管理模块:由卡片提供方开发者实现,负责对卡片管理服务分配的卡片实例进行持久化管理。
49
50- 通信适配层:由OpenHarmony SDK提供,负责与卡片管理服务通信,用于将卡片的更新数据主动推送到卡片管理服务。
51
52> **说明:**
53> 实际开发时只需要作为卡片提供方进行卡片内容的开发,卡片使用方和卡片管理服务由系统自动处理。
54
55
56## 接口说明
57
58FormAbility生命周期接口如下:
59
60| 接口名 | 描述 |
61| -------- | -------- |
62| onCreate(want: Want): formBindingData.FormBindingData | 卡片提供方接收创建卡片的通知接口。 |
63| onCastToNormal(formId: string): void | 卡片提供方接收临时卡片转常态卡片的通知接口 |
64| onUpdate(formId: string): void | 卡片提供方接收更新卡片的通知接口。 |
65| onVisibilityChange(newStatus: { [key: string]: number }): void | 卡片提供方接收修改可见性的通知接口。 |
66| onEvent(formId: string, message: string): void | 卡片提供方接收处理卡片事件的通知接口。 |
67| onDestroy(formId: string): void | 卡片提供方接收销毁卡片的通知接口。 |
68| onAcquireFormState?(want: Want): formInfo.FormState | 卡片提供方接收查询卡片状态的通知接口。 |
69| onShare?(formId: string): {[key: string]: any} | 卡片提供方接收卡片分享的通知接口。 |
70
71FormProvider类有如下API接口,具体的API介绍详见[接口文档](../reference/apis/js-apis-app-form-formProvider.md)。
72
73
74| 接口名 | 描述 |
75| -------- | -------- |
76| setFormNextRefreshTime(formId: string, minute: number, callback: AsyncCallback<void>): void; | 设置指定卡片的下一次更新时间。 |
77| setFormNextRefreshTime(formId: string, minute: number): Promise<void>; | 设置指定卡片的下一次更新时间,以promise方式返回。 |
78| updateForm(formId: string, formBindingData: FormBindingData, callback: AsyncCallback<void>): void; | 更新指定的卡片。 |
79| updateForm(formId: string, formBindingData: FormBindingData): Promise<void>; | 更新指定的卡片,以promise方式返回。 |
80
81
82formBindingData类有如下API接口,具体的API介绍详见[接口文档](../reference/apis/js-apis-app-form-formBindingData.md)。
83
84
85| 接口名 | 描述 |
86| -------- | -------- |
87| createFormBindingData(obj?: Object \| string): FormBindingData | 创建一个FormBindingData对象。 |
88
89
90## 开发步骤
91
92FA卡片开发,即基于[FA模型](fa-model-development-overview.md)的卡片提供方开发,主要涉及如下关键步骤:
93
94- [实现卡片生命周期接口](#实现卡片生命周期接口):开发FormAbility生命周期回调函数。
95
96- [配置卡片配置文件](#配置卡片配置文件):配置应用配置文件config.json97
98- [卡片信息的持久化](#卡片信息的持久化):对卡片信息进行持久化管理。
99
100- [卡片数据交互](#卡片数据交互):通过updateForm()更新卡片显示的信息。
101
102- [开发卡片页面](#开发卡片页面):使用HML+CSS+JSON开发JS卡片页面。
103
104- [开发卡片事件](#开发卡片事件):为卡片添加router事件和message事件。
105
106
107### 实现卡片生命周期接口
108
109创建FA模型的卡片,需实现卡片的生命周期接口。先参考[IDE开发服务卡片指南](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-development-service-widget-0000001263280425)生成服务卡片模板。
110
1111. 在form.ts中,导入相关模块
112
113   ```ts
114   import formBindingData from '@ohos.app.form.formBindingData';
115   import formInfo from '@ohos.app.form.formInfo';
116   import formProvider from '@ohos.app.form.formProvider';
117   import dataStorage from '@ohos.data.storage';
118   ```
119
1202. 在form.ts中,实现卡片生命周期接口
121
122   ```ts
123   export default {
124     onCreate(want) {
125       console.info('FormAbility onCreate');
126       // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
127       let obj = {
128         "title": "titleOnCreate",
129         "detail": "detailOnCreate"
130       };
131       let formData = formBindingData.createFormBindingData(obj);
132       return formData;
133     },
134     onCastToNormal(formId) {
135       // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
136       console.info('FormAbility onCastToNormal');
137     },
138     onUpdate(formId) {
139       // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
140       console.info('FormAbility onUpdate');
141       let obj = {
142         "title": "titleOnUpdate",
143         "detail": "detailOnUpdate"
144       };
145       let formData = formBindingData.createFormBindingData(obj);
146       formProvider.updateForm(formId, formData).catch((error) => {
147         console.info('FormAbility updateForm, error:' + JSON.stringify(error));
148       });
149     },
150     onVisibilityChange(newStatus) {
151       // 使用方发起可见或者不可见通知触发,提供方需要做相应的处理,仅系统应用生效
152       console.info('FormAbility onVisibilityChange');
153     },
154     onEvent(formId, message) {
155       // 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
156       console.info('FormAbility onEvent');
157     },
158     onDestroy(formId) {
159       // 删除卡片实例数据
160       console.info('FormAbility onDestroy');
161     },
162     onAcquireFormState(want) {
163       console.info('FormAbility onAcquireFormState');
164       return formInfo.FormState.READY;
165     },
166   }
167   ```
168
169> **说明:**
170> FormAbility不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务。
171
172### 配置卡片配置文件
173
174卡片需要在应用配置文件config.json中进行配置。
175
176- js模块,用于对应卡片的js相关资源,内部字段结构说明:
177    | 属性名称 | 含义 | 数据类型 | 是否可缺省 |
178  | -------- | -------- | -------- | -------- |
179  | name | 表示JS Component的名字。该标签不可缺省,默认值为default。 | 字符串 | 否 |
180  | pages | 表示JS Component的页面用于列举JS Component中每个页面的路由信息[页面路径+页面名称]。该标签不可缺省,取值为数组,数组第一个元素代表JS FA首页。 | 数组 | 否 |
181  | window | 用于定义与显示窗口相关的配置。 | 对象 | 可缺省 |
182  | type | 表示JS应用的类型。取值范围如下:<br/>normal:标识该JS&nbsp;Component为应用实例。<br/>form:标识该JS&nbsp;Component为卡片实例。 | 字符串 | 可缺省,缺省值为“normal” |
183  | mode | 定义JS组件的开发模式。 | 对象 | 可缺省,缺省值为空 |
184
185  配置示例如下:
186
187
188  ```json
189  "js": [{
190     "name": "widget",
191     "pages": ["pages/index/index"],
192     "window": {
193         "designWidth": 720,
194         "autoDesignWidth": true
195     },
196     "type": "form"
197  }]
198  ```
199
200- abilities模块,用于对应卡片的FormAbility,内部字段结构说明:
201    | 属性名称 | 含义 | 数据类型 | 是否可缺省 |
202  | -------- | -------- | -------- | -------- |
203  | name | 表示卡片的类名。字符串最大长度为127字节。 | 字符串 | 否 |
204  | description | 表示卡片的描述。取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。字符串最大长度为255字节。 | 字符串 | 可缺省,缺省为空。 |
205  | isDefault | 表示该卡片是否为默认卡片,每个Ability有且只有一个默认卡片。<br/>true:默认卡片。<br/>false:非默认卡片。 | 布尔值 | 否 |
206  | type | 表示卡片的类型。取值范围如下:<br/>JS:JS卡片。 | 字符串 | 否 |
207  | colorMode | 表示卡片的主题样式,取值范围如下:<br/>auto:自适应。<br/>dark:深色主题。<br/>light:浅色主题。 | 字符串 | 可缺省,缺省值为“auto”。 |
208  | supportDimensions | 表示卡片支持的外观规格,取值范围:<br/>1&nbsp;\*&nbsp;2:表示1行2列的二宫格。<br/>2&nbsp;\*&nbsp;2:表示2行2列的四宫格。<br/>2&nbsp;\*&nbsp;4:表示2行4列的八宫格。<br/>4&nbsp;\*&nbsp;4:表示4行4列的十六宫格。 | 字符串数组 | 否 |
209  | defaultDimension | 表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中。 | 字符串 | 否 |
210  | updateEnabled | 表示卡片是否支持周期性刷新,取值范围:<br/>true:表示支持周期性刷新,可以在定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)两种方式任选其一,优先选择定时刷新。<br/>false:表示不支持周期性刷新。 | 布尔类型 | 否 |
211  | scheduledUpdateTime | 表示卡片的定点刷新的时刻,采用24小时制,精确到分钟。<br/>updateDuration参数优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。 | 字符串 | 可缺省,缺省值为“0:0”。 |
212  | updateDuration | 表示卡片定时刷新的更新周期,单位为30分钟,取值为自然数。<br/>当取值为0时,表示该参数不生效。<br/>当取值为正整数N时,表示刷新周期为30\*N分钟。<br/>updateDuration参数优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。 | 数值 | 可缺省,缺省值为“0”。 |
213  | formConfigAbility | 表示卡片的配置跳转链接,采用URI格式。 | 字符串 | 可缺省,缺省值为空。 |
214  | formVisibleNotify | 标识是否允许卡片使用卡片可见性通知。 | 字符串 | 可缺省,缺省值为空。 |
215  | jsComponentName | 表示JS卡片的Component名称。字符串最大长度为127字节。 | 字符串 | 否 |
216  | metaData | 表示卡片的自定义信息,包含customizeData数组标签。 | 对象 | 可缺省,缺省值为空。 |
217  | customizeData | 表示自定义的卡片信息。 | 对象数组 | 可缺省,缺省值为空。 |
218
219  配置示例如下:
220
221
222  ```json
223     "abilities": [{
224         "name": "FormAbility",
225         "description": "This is a FormAbility",
226         "formsEnabled": true,
227         "icon": "$media:icon",
228         "label": "$string:form_FormAbility_label",
229         "srcPath": "FormAbility",
230         "type": "service",
231         "srcLanguage": "ets",
232         "formsEnabled": true,
233         "formConfigAbility": "ability://com.example.entry.EntryAbility",
234         "forms": [{
235             "colorMode": "auto",
236             "defaultDimension": "2*2",
237             "description": "This is a service widget.",
238             "formVisibleNotify": true,
239             "isDefault": true,
240             "jsComponentName": "widget",
241             "name": "widget",
242             "scheduledUpdateTime": "10:30",
243             "supportDimensions": ["2*2"],
244             "type": "JS",
245             "updateEnabled": true
246         }]
247     }]
248  ```
249
250
251### 卡片信息的持久化
252
253因大部分卡片提供方都不是常驻服务,只有在需要使用时才会被拉起获取卡片信息,且卡片管理服务支持对卡片进行多实例管理,卡片ID对应实例ID,因此若卡片提供方支持对卡片数据进行配置,则需要对卡片的业务数据按照卡片ID进行持久化管理,以便在后续获取、更新以及拉起时能获取到正确的卡片业务数据。
254
255
256```ts
257const DATA_STORAGE_PATH = "/data/storage/el2/base/haps/form_store";
258async function storeFormInfo(formId: string, formName: string, tempFlag: boolean) {
259    // 此处仅对卡片ID:formId,卡片名:formName和是否为临时卡片:tempFlag进行了持久化
260    let formInfo = {
261        "formName": formName,
262        "tempFlag": tempFlag,
263        "updateCount": 0
264    };
265    try {
266        const storage = await dataStorage.getStorage(DATA_STORAGE_PATH);
267        // put form info
268        await storage.put(formId, JSON.stringify(formInfo));
269        console.info(`storeFormInfo, put form info successfully, formId: ${formId}`);
270        await storage.flush();
271    } catch (err) {
272        console.error(`failed to storeFormInfo, err: ${JSON.stringify(err)}`);
273    }
274}
275
276...
277    onCreate(want) {
278        console.info('FormAbility onCreate');
279
280        let formId = want.parameters["ohos.extra.param.key.form_identity"];
281        let formName = want.parameters["ohos.extra.param.key.form_name"];
282        let tempFlag = want.parameters["ohos.extra.param.key.form_temporary"];
283        // 将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用
284        // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
285        storeFormInfo(formId, formName, tempFlag);
286
287        let obj = {
288            "title": "titleOnCreate",
289            "detail": "detailOnCreate"
290        };
291        let formData = formBindingData.createFormBindingData(obj);
292        return formData;
293    }
294...
295```
296
297且需要适配onDestroy卡片删除通知接口,在其中实现卡片实例数据的删除。
298
299
300```ts
301const DATA_STORAGE_PATH = "/data/storage/el2/base/haps/form_store";
302async function deleteFormInfo(formId: string) {
303    try {
304        const storage = await dataStorage.getStorage(DATA_STORAGE_PATH);
305        // del form info
306        await storage.delete(formId);
307        console.info(`deleteFormInfo, del form info successfully, formId: ${formId}`);
308        await storage.flush();
309    } catch (err) {
310        console.error(`failed to deleteFormInfo, err: ${JSON.stringify(err)}`);
311    }
312}
313
314...
315    onDestroy(formId) {
316        console.info('FormAbility onDestroy');
317        // 删除之前持久化的卡片实例数据
318        // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
319        deleteFormInfo(formId);
320    }
321...
322```
323
324具体的持久化方法可以参考[数据管理开发指导](../database/app-data-persistence-overview.md)。
325
326需要注意的是,卡片使用方在请求卡片时传递给提供方应用的Want数据中存在临时标记字段,表示此次请求的卡片是否为临时卡片:
327
328- 常态卡片:卡片使用方会持久化的卡片;
329
330- 临时卡片:卡片使用方不会持久化的卡片;
331
332由于临时卡片的数据具有非持久化的特殊性,某些场景例如卡片服务框架死亡重启,此时临时卡片数据在卡片管理服务中已经删除,且对应的卡片ID不会通知到提供方,所以卡片提供方需要自己负责清理长时间未删除的临时卡片数据。同时对应的卡片使用方可能会将之前请求的临时卡片转换为常态卡片。如果转换成功,卡片提供方也需要对对应的临时卡片ID进行处理,把卡片提供方记录的临时卡片数据转换为常态卡片数据,防止提供方在清理长时间未删除的临时卡片时,把已经转换为常态卡片的临时卡片信息删除,导致卡片信息丢失。
333
334
335### 卡片数据交互
336
337当卡片应用需要更新数据时(如触发了定时更新或定点更新),卡片应用获取最新数据,并调用updateForm()接口更新主动触发卡片的更新。
338
339
340```ts
341onUpdate(formId) {
342    // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
343    console.info('FormAbility onUpdate');
344    let obj = {
345        "title": "titleOnUpdate",
346        "detail": "detailOnUpdate"
347    };
348    let formData = formBindingData.createFormBindingData(obj);
349    // 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变
350    formProvider.updateForm(formId, formData).catch((error) => {
351        console.info('FormAbility updateForm, error:' + JSON.stringify(error));
352    });
353}
354```
355
356
357### 开发卡片页面
358
359开发者可以使用类Web范式(HML+CSS+JSON)开发JS卡片页面。生成如下卡片页面,可以这样配置卡片页面文件:
360
361![widget-development-fa](figures/widget-development-fa.png)
362
363> **说明:**
364> FA模型当前仅支持JS扩展的类Web开发范式来实现卡片的UI。
365
366- HML:使用类Web范式的组件描述卡片的页面信息。
367
368  ```html
369  <div class="container">
370    <stack>
371      <div class="container-img">
372        <image src="/common/widget.png" class="bg-img"></image>
373      </div>
374      <div class="container-inner">
375        <text class="title">{{title}}</text>
376        <text class="detail_text" onclick="routerEvent">{{detail}}</text>
377      </div>
378    </stack>
379  </div>
380  ```
381
382- CSS:HML中类Web范式组件的样式信息。
383
384  ```css
385  .container {
386    flex-direction: column;
387    justify-content: center;
388    align-items: center;
389  }
390
391  .bg-img {
392    flex-shrink: 0;
393    height: 100%;
394  }
395
396  .container-inner {
397    flex-direction: column;
398    justify-content: flex-end;
399    align-items: flex-start;
400    height: 100%;
401    width: 100%;
402    padding: 12px;
403  }
404
405  .title {
406    font-size: 19px;
407    font-weight: bold;
408    color: white;
409    text-overflow: ellipsis;
410    max-lines: 1;
411  }
412
413  .detail_text {
414    font-size: 16px;
415    color: white;
416    opacity: 0.66;
417    text-overflow: ellipsis;
418    max-lines: 1;
419    margin-top: 6px;
420  }
421  ```
422
423- JSON:卡片页面中的数据和事件交互。
424
425  ```json
426  {
427    "data": {
428      "title": "TitleDefault",
429      "detail": "TextDefault"
430    },
431    "actions": {
432      "routerEvent": {
433        "action": "router",
434        "abilityName": "com.example.entry.EntryAbility",
435        "params": {
436          "message": "add detail"
437        }
438      }
439    }
440  }
441  ```
442
443
444### 开发卡片事件
445
446卡片支持为组件设置交互事件(action),包括router事件和message事件,其中router事件用于Ability跳转,message事件用于卡片开发人员自定义点击事件。关键步骤说明如下:
447
4481. 在hml中为组件设置onclick属性,其值对应到json文件的actions字段中。
449
4502. 如何设置router事件:
451   - action属性值为"router";
452   - abilityName为跳转目标的Ability名(支持跳转FA模型的PageAbility组件和Stage模型的UIAbility组件),如目前DevEco创建的FA模型的UIAbility默认名为com.example.entry.EntryAbility453   - params为传递给跳转目标Ability的自定义参数,可以按需填写。其值可以在目标Ability启动时的want中的parameters里获取。如FA模型EntryAbility的onCreate生命周期里可以通过featureAbility.getWant()获取到want,然后在其parameters字段下获取到配置的参数;
454
4553. 如何设置message事件:
456   - action属性值为"message";
457   - params为message事件的用户自定义参数,可以按需填写。其值可以在卡片生命周期函数onEvent中的message里获取;
458
459示例如下:
460
461- hml文件
462
463  ```html
464  <div class="container">
465      <stack>
466          <div class="container-img">
467              <image src="/common/widget.png" class="bg-img"></image>
468          </div>
469          <div class="container-inner">
470              <text class="title" onclick="routerEvent">{{title}}</text>
471              <text class="detail_text" onclick="messageEvent">{{detail}}</text>
472          </div>
473      </stack>
474  </div>
475  ```
476
477- css文件
478
479  ```css
480  .container {
481    flex-direction: column;
482    justify-content: center;
483    align-items: center;
484  }
485
486  .bg-img {
487    flex-shrink: 0;
488    height: 100%;
489  }
490
491  .container-inner {
492    flex-direction: column;
493    justify-content: flex-end;
494    align-items: flex-start;
495    height: 100%;
496    width: 100%;
497    padding: 12px;
498  }
499
500  .title {
501    font-size: 19px;
502    font-weight: bold;
503    color: white;
504    text-overflow: ellipsis;
505    max-lines: 1;
506  }
507
508  .detail_text {
509    font-size: 16px;
510    color: white;
511    opacity: 0.66;
512    text-overflow: ellipsis;
513    max-lines: 1;
514    margin-top: 6px;
515  }
516  ```
517
518- json文件
519
520  ```json
521  {
522    "data": {
523      "title": "TitleDefault",
524      "detail": "TextDefault"
525    },
526    "actions": {
527      "routerEvent": {
528        "action": "router",
529        "abilityName": "com.example.entry.EntryAbility",
530        "params": {
531          "message": "add detail"
532        }
533      },
534      "messageEvent": {
535        "action": "message",
536        "params": {
537          "message": "add detail"
538        }
539      }
540    }
541  }
542  ```
543