HarmonyOS之深入解析服务卡片的使用
Posted Forever_wj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HarmonyOS之深入解析服务卡片的使用相关的知识,希望对你有一定的参考价值。
一、概述
① 基本概念
- 服务卡片(以下简称“卡片”)是 FA 的一种界面展示形式,将 FA 的重要信息或操作前置到卡片,以达到服务直达,减少体验层级的目的。
- 卡片常用于嵌入到其他应用(当前只支持系统应用)中作为其界面的一部分显示,并支持拉起页面,发送消息等基础的交互功能。卡片使用方负责显示卡片。
- 服务卡片如下图所示:
- 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。
- 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。
- 卡片提供方:提供卡片显示内容的 HarmonyOS 应用或原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。
- 卡片使用方和提供方不要求常驻运行,在需要添加/删除/请求更新卡片时,卡片管理服务会拉起卡片提供方获取卡片信息。
- 卡片提供方控制卡片实际显示的内容、控件布局以及控件点击事件。
② 运作机制
- 卡片管理服务包含以下模块:
-
- 周期性刷新:在卡片添加后,根据卡片的刷新策略启动定时任务周期性触发卡片的刷新。
-
- 卡片缓存管理:在卡片添加到卡片管理服务后,对卡片的视图信息进行缓存,以便下次获取卡片时可以直接返回缓存数据,降低时延。
-
- 卡片生命周期管理:对于卡片切换到后台或者被遮挡时,暂停卡片的刷新;以及卡片的升级/卸载场景下对卡片数据的更新和清理。
-
- 卡片使用方对象管理:对卡片使用方的 RPC 对象进行管理,用于使用方请求进行校验以及对卡片更新后的回调处理。
-
- 通信适配层:负责与卡片使用方和提供方进行 RPC 通信。
- 卡片提供方包含以下模块:
-
- 卡片服务:由卡片提供方开发者实现,开发者实现 onCreateForm、onUpdateForm 和 onDeleteForm 处理创建卡片、更新卡片以及删除卡片等请求,提供相应的卡片服务。
-
- 卡片提供方实例管理模块:由卡片提供方开发者实现,负责对卡片管理服务分配的卡片实例进行持久化管理。
-
- 通信适配层:由 HarmonyOS SDK 提供,负责与卡片管理服务通信,用于将卡片的更新数据主动推送到卡片管理服务。
二、API 说明
- 卡片提供方接口功能:
类名 | 接口名 | 描述 |
---|---|---|
Ability | ProviderFormInfo onCreateForm(Intent intent) | 卡片提供方接收创建卡片通知接口 |
void onUpdateForm(long formId) | 卡片提供方接收更新卡片通知接口 | |
void onDeleteForm(long formId) | 卡片提供方接收删除卡片通知接口 | |
void onTriggerFormEvent(long formId, String message) | 卡片提供方处理卡片事件接口(JS卡片使用) | |
boolean updateForm(long formId, ComponentProvider component) | 卡片提供方主动更新卡片(Java卡片使用) | |
boolean updateForm(long formId, FormBindingData formBindingData) | 卡片提供方主动更新卡片(JS卡片使用),仅更新formBindingData中携带的信息,卡片中其余信息保持不变 | |
void onCastTempForm(long formId) | 卡片提供方接收临时卡片转常态卡片通知 | |
void onEventNotify(Map < Long, Integer > formEvents) | 卡片提供方接收到事件通知,其中Ability.FORM_VISIBLE表示卡片可见通知, Ability.FORM_INVISIBLE表示卡片不可见通知 | |
FormState onAcquireFormState(Intent intent) | 卡片提供方接收查询卡片状态通知接口,默认返回卡片初始状态 | |
ProviderFormInfo | ProviderFormInfo(int resId, Context context) | Java卡片返回对象构造函数 |
ProviderFormInfo() | JS卡片返回对象构造函数 | |
void mergeActions(ComponentProvider componentProviderActions) | 在提供方侧调用该接口,将开发者在ComponentProvider中设置的actions配置数据合并到当前对象中 | |
void setJsBindingData(FormBindingData data) | 设置JS卡片的内容信息(JS卡片使用) |
- onEventNotify 仅系统应用才会回调,其他接口回调时机如下图:
- 卡片管理服务不负责保持卡片的活跃状态(设置了定时更新的除外),当使用方作出相应的请求时,管理服务会拉起提供方并回调相应接口。
三、Java 卡片与 JS 卡片选型
- Java/JS 卡片场景能力差异如下表所示:
场景 | Java卡片 | JS卡片 | 支持的版本 |
---|---|---|---|
实时刷新(类似时钟) | Java使用ComponentProvider做实时刷新代价比较大 | JS可以做到端侧刷新,但是需要定制化组件 | HarmonyOS 2.0及以上 |
开发方式 | Java UI在卡片提供方需要同时对数据和组件进行处理,生成ComponentProvider远端渲染 | JS卡片在使用方加载渲染,提供方只要处理数据、组件和逻辑分离 | HarmonyOS 2.0及以上 |
组件支持 | Text、Image、DirectionalLayout、PositionLayout、DependentLayout | div、list、list-item、swiper、stack、image、text、span、progress、button(定制:chart 、clock、calendar) | HarmonyOS 2.0及以上 |
卡片内动效 | 不支持 | 暂不开放 | HarmonyOS 2.0及以上 |
阴影模糊 | 不支持 | 支持 | HarmonyOS 2.0及以上 |
动态适应布局 | 不支持 | 支持 | HarmonyOS 2.0及以上 |
自定义卡片跳转页面 | 不支持 | 支持 | HarmonyOS 2.0及以上 |
- 综上所述,JS 卡片比 Java 卡片支持的控件和能力都更丰富:
-
- Java 卡片:适合作为一个直达入口,没有复杂的页面和事件。
-
- JS 卡片:适合有复杂界面的卡片。
- 对于同一个 Page ability,在 config.json 中最多支持配置 16 张卡片。
三、JS 卡片
① 使用 hml+css+json 开发 JS 卡片页面
- 使用 DevEco Studio 创建卡片工程
-
- 创建成功后,在 config.json 的 module 中会生成 js 模块,用于对应卡片的 js 相关资源,配置示例如下:
"js": [
{
"name": "card",
"pages": [
"pages/index/index"
],
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"type": "form"
}
]
-
- config.json 文件“abilities”配置 forms 模块细节如下:
"forms": [
{
"name": "Form_Js",
"description": "form_description",
"type": "JS",
"jsComponentName": "card",
"formConfigAbility": "ability://com.huawei.demo.SecondFormAbility",
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true,
"scheduledUpdateTime": "10:30",
"updateDuration": 1,
"defaultDimension": "2*2",
"supportDimensions": [
"2*2",
"2*4",
"4*4"
],
"metaData": {
"customizeData": [
{
"name": "originWidgetName",
"value": "com.huawei.weather.testWidget"
}
]
}
}
]
-
- 配置文件中,应注意如下配置:
-
-
- “js”模块中的 name 字段要与“forms”模块中的 jsComponentName 字段的值一致,为 js 资源的实例名。
-
-
-
- “forms”模块中的 name 为卡片名,即在 onCreateForm 中根据 AbilitySlice.PARAM_FORM_NAME_KEY 可取到的值。
-
-
-
- 卡片的 Ability 中还需要配置"visible": true 和"formsEnabled": true。
-
-
-
- 定时刷新和定点刷新都配置的情况下,定时刷新优先。
-
-
-
- defaultDimension 是默认规格,必须设置。
-
-
- forms 对象的内部结构说明:
属性名称 | 子属性名称 | 含义 | 数据类型 | 是否可缺省 |
---|---|---|---|---|
name | - | 表示卡片的类名,字符串最大长度为127字节 | 字符串 | 否 |
description | - | 表示卡片的描述,取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。字符串最大长度为255字节 | 字符串 | 可缺省,缺省为空 |
isDefault | - | 表示该卡片是否为默认卡片,每个Ability有且只有一个默认卡片 true:默认卡片 false:非默认卡片 | 布尔值 | 否 |
type | - | 表示卡片的类型,取值范围如下: Java:Java卡片 JS:JS卡片 | 字符串 | 否 |
colorMode | - | 表示卡片的主题样式,取值范围如下: auto:自适应 dark:深色主题 light:浅色主题 | 字符串 | 可缺省,缺省值为“auto” |
supportDimensions | - | 表示卡片支持的外观规格,取值范围: 1*2:表示1行2列的二宫格 2*2:表示2行2列的四宫格 2*4:表示2行4列的八宫格 4*4:表示4行4列的十六宫格 | 字符串数组 | 否 |
defaultDimension | - | 表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中 | 字符串 | 否 |
landscapeLayouts | - | 表示卡片外观规格对应的横向布局文件,与supportDimensions中的规格一一对应,仅当卡片类型为Java卡片时,需要配置该标签 | 字符串数组 | 否 |
portraitLayouts | - | 表示卡片外观规格对应的竖向布局文件,与supportDimensions中的规格一一对应,仅当卡片类型为Java卡片时,需要配置该标签 | 字符串数组 | 否 |
updateEnabled | - | 表示卡片是否支持周期性刷新,取值范围: true:表示支持周期性刷新,可以在定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)两种方式任选其一,优先选择定时刷新 false:表示不支持周期性刷新 | 布尔类型 | 否 |
scheduledUpdateTime | - | 表示卡片的定点刷新的时刻,采用24小时制,精确到分钟 | 字符串 | 可缺省,缺省值为“0:0” |
updateDuration | - | 表示卡片定时刷新的更新周期,单位为30分钟,取值为自然数: 当取值为0时,表示该参数不生效;当取值为正整数N时,表示刷新周期为30*N分钟 | 数值 | 可缺省,缺省值为“0” |
formConfigAbility | - | 表示卡片的配置跳转链接,采用URI格式 | 字符串 | 可缺省,缺省值为空 |
jsComponentName | - | 表示JS卡片的Component名称,字符串最大长度为127字节,仅当卡片类型为JS卡片时,需要配置该标签 | 字符串 | 否 |
metaData | - | 表示卡片的自定义信息,包含customizeData数组标签 | 对象 | 可缺省,缺省值为空 |
customizeData | - | 表示自定义的卡片信息 | 对象数组 | 可缺省,缺省值为空 |
name | 表示数据项的键名称,字符串最大长度为255字节 | 字符串 | 可缺省,缺省值为空 | |
value | 表示数据项的值,字符串最大长度为255字节 | 字符串 | 可缺省,缺省值为空 |
- 创建一个 FormAbility,覆写卡片相关回调函数
-
- 回调函数
-
-
- onCreateForm(Intent intent)
-
-
-
- onUpdateForm(long formId)
-
-
-
- onDeleteForm(long formId)
-
-
-
- onCastTempForm(long formId)
-
-
-
- onEventNotify(Map<Long, Integer> formEvents)
-
-
-
- onTriggerFormEvent(long formId, String message)
-
-
-
- onAcquireFormState(Intent intent)
-
-
- 当卡片使用方请求获取卡片时,卡片提供方会被拉起并调用 onCreateForm(Intent intent) 回调,intent 中会带有卡片 ID、卡片名称和卡片外观规格信息,可按需获取使用。
-
- 开发 JS 卡片时,FormAbility 可以继承 AceAbility 或 Ability,继承 Ability 时,需在 onStart() 方法中额外设置路由信息。
-
- FormAbility 继承 AceAbility 的代码示例:
public class FormAbility extends AceAbility {
......
public static long formId = -1;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
}
@Override
protected ProviderFormInfo onCreateForm(Intent intent) {
long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, 0);
String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);
boolean tempFlag = intent.getBooleanParam(AbilitySlice.PARAM_FORM_TEMPORARY_KEY, false);
HiLog.info(LABEL_LOG, "onCreateForm: " + formId + " " + formName + " " + specificationId);
FormBindingData formBindingData = new FormBindingData("{\\"temperature\\": \\"60°\\"}");
ProviderFormInfo formInfo = new ProviderFormInfo();
formInfo.setJsBindingData(formBindingData);
return formInfo;
}
@Override
protected void onDeleteForm(long formId) {
// 删除卡片实例数据
super.onDeleteForm(formId);
......
}
@Override
protected void onUpdateForm(long formId) {
// 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要覆写该方法以支持数据更新
super.onUpdateForm(formId);
......
}
@Override
protected void onTriggerFormEvent(long formId, String message) {
// 若卡片支持触发事件,则需要覆写该方法并实现对事件的触发
super.onTriggerFormEvent(formId, message);
......
}
@Override
protected void onCastTempForm(long formId) {
//使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
super.onCastTempForm (formId);
......
}
@Override
protected void onEventNotify(Map<Long, Integer> formEvents) {
//使用方发起可见或者不可见通知触发,提供方需要做相应的处理
super.onEventNotify(formEvents);
......
}
@Override
protected FormState onAcquireFormState(Intent intent) {
ElementName elementName = intent.getElement();
if (elementName == null) {
HiLog.info(LABEL_LOG, "onAcquireFormState bundleName and abilityName are not set in intent");
return FormState.UNKNOWN;
}
String bundleName = elementName.getBundleName();
String abilityName = elementName.getAbilityName();
String moduleName = intent.getStringParam(AbilitySlice.PARAM_MODULE_NAME_KEY);
String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);
if ("form_name2".equals(formName)) {
return FormState.DEFAULT;
}
return FormState.READY;
}
}
-
- FormAbility 继承 Ability 的代码示例:
public class FormAbility extends Ability {
......
public static long formId = -1;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(FormAbilitySlice.class.getName()); //设置路由
}
@Override
protected ProviderFormInfo onCreateForm(Intent intent) {
long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, 0);
String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);
boolean tempFlag = intent.getBooleanParam(AbilitySlice.PARAM_FORM_TEMPORARY_KEY, false);
HiLog.info(LABEL_LOG, "onCreateForm: " + formId + " " + formName + " " + specificationId);
FormBindingData formBindingData = new FormBindingData("{\\"temperature\\": \\"60°\\"}");
ProviderFormInfo formInfo = new ProviderFormInfo();
formInfo.setJsBindingData(formBindingData);
return formInfo;
}
@Override
protected void onDeleteForm(long formId) {
// 删除卡片实例数据
super.onDeleteForm(formId);
......
}
@Override
protected void onUpdateForm(long formId) {
// 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要覆写该方法以支持数据更新
super.onUpdateForm(formId);
......
}
@Override
protected void onTriggerFormEvent(long formId, String message) {
// 若卡片支持触发事件,则需要覆写该方法并实现对事件的触发
super.onTriggerFormEvent(formId, message);
......
}
@Override
protected void onCastTempForm(long formId) {
//使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
super.onCastTempForm (formId);
......
}
@Override
protected void onEventNotify(Map<Long, Integer> formEvents) {
//使用方发起可见或者不可见通知触发,提供方需要做相应的处理
super.onEventNotify(formEvents);
......
}
@Override
protected FormState onAcquireFormState(Intent intent) {
ElementName elementName = intent.getElement();
if (elementName == null) {
HiLog.info(LABEL_LOG, "onAcquireFormState bundleName and abilityName are not set in intent");
return FormState.UNKNOWN;
}
String bundleName = elementName.getBundleName();
String abilityName = elementName.getAbilityName();
String moduleName = intent.getStringParam(AbilitySlice.PARAM_MODULE_NAME_KEY);
String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
int specificationId = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);
if ("form_name2".equals(formName)) {
return FormState.DEFAULT;
}
return FormState.READY;
}
}
- 卡片信息持久化
-
- 因大部分卡片提供方都不是常驻服务,只有在需要使用时才会被拉起获取卡片信息,且卡片管理服务支持对卡片进行多实例管理,卡片 ID 对应实例 ID,因此若卡片提供方支持对卡片数据进行配置,则需要对卡片的业务数据按照卡片 ID 进行持久化管理,以便在后续获取、更新以及拉起时能获取到正确的卡片业务数据。且需要适配 onDeleteForm(long formId) 卡片删除通知接口,在其中实现卡片实例数据的删除。
-
- 常态卡片:卡片使用方会持久化的卡片;
-
- 临时卡片:卡片使用方不会持久化的卡片;
-
- 需要注意的是,卡片使用方在请求卡片时传递给提供方应用的 Intent 数据中存在临时标记字段,表示此次请求的卡片是否为临时卡片,由于临时卡片的数据具有非持久化的特殊性,某些场景比如卡片服务框架死亡重启,此时临时卡片数据在卡片管理服务中已经删除,且对应的卡片 ID 不会通知到提供方,所以卡片提供方需要自己负责清理长时间未删除的临时卡片数据。同时对应的卡片使用方可能会将之前请求的临时卡片转换为常态卡片。如果转换成功,卡片提供方也需要对对应的临时卡片 ID 进行处理,把卡片提供方记录的临时卡片数据转换为常态卡片数据,防止提供方在清理长时间未删除的临时卡片时,把已经转换为常态卡片的临时卡片信息删除,导致卡片信息丢失。
@Override
protected ProviderFormInfo onCreateForm(Intent intent) {
long formId = intent.getIntParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, -1L);
String formName = params.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
int specificationId = params.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, 0);
boolean tempFlag = params.getBooleanParam(AbilitySlice.PARAM_FORM_TEMPORARY_KEY, false);
HiLog.info(LABEL_LOG, "onCreateForm: " + formId + " " + formName + " " + specificationId);
.......
// 由开发人员自行实现,将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用
storeFormInfo(formId, formName, specificationId, formData);
......
HiLog.info(LABEL_LOG, "onCreateForm finish.......");
return formInfo;
}
@Override
protected void onDeleteForm(long formId) {
super.onDeleteForm(formId);
// 由开发人员自行实现,删除卡片实例数据
deleteFormInfo(formId);
......
}
@Override
protected void onCastTempForm(long formId) {
// 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
super.onCastTempForm (formId);
......
}
- 卡片数据交互
-
- 当卡片应用需要更新数据时(如触发了定时更新或定点更新),卡片应用获取最新数据,并调用 updateForm 接口更新卡片。示例如下:
@Override
protected void onUpdateForm(long formId) {
super.onUpdateForm(formId);
ZSONObject zsonObject = new ZSONObject();
zsonObject.put("temperature", "90°");
FormBindingData formBindingData = new FormBindingData(zsonObject);
// 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变
if (!updateForm(formId, formBindingData)) {
// err process
}
}
- 开发 JS 卡片页面
-
- JS 卡片页面与普通 FA 类似通过 hml+css+json 开发,示例如下:
hml:
<div class="container">
<stack class="stack_container">
<image class = "img" src="common/clouds.png"></image>
<div style="flex-direction: column;">
<text class="txt_city" onclick="messageEvent">{{city}}</text>
<text class="txt_temperature" onclick="routerEvent">{{temperature}}</text>
</div>
</stack>
</div>
css:
.container {
flex-direction: column;
justify-content: center;
align-items: center;
}
.stack_container {
width: 100%;
height: 100%;
background-image: url("/common/weather-background-day.png");
background-size: cover;
}
...
json:
{
"data": {
"temperature": "35°",
"city": "hangzhou"
HarmonyOS之深入解析WLAN的功能和使用