以下内容介绍基于类Web范式的JS UI卡片开发指南。
运作机制
卡片框架的运作机制如图1所示。
图1 卡片框架运作机制(Stage模型)
卡片使用方包含以下模块:
- 卡片使用:包含卡片的创建、删除、请求更新等操作。
- 通信适配层:由OpenHarmony SDK提供,负责与卡片管理服务通信,用于将卡片的相关操作到卡片管理服务。
卡片管理服务包含以下模块:
- 周期性刷新:在卡片添加后,根据卡片的刷新策略启动定时任务周期性触发卡片的刷新。
- 卡片缓存管理:在卡片添加到卡片管理服务后,对卡片的视图信息进行缓存,以便下次获取卡片时可以直接返回缓存数据,降低时延。
- 卡片生命周期管理:对于卡片切换到后台或者被遮挡时,暂停卡片的刷新;以及卡片的升级/卸载场景下对卡片数据的更新和清理。
- 卡片使用方对象管理:对卡片使用方的RPC对象进行管理,用于使用方请求进行校验以及对卡片更新后的回调处理。
- 通信适配层:负责与卡片使用方和提供方进行RPC通信。
卡片提供方包含以下模块:
- 卡片服务:由卡片提供方开发者实现,开发者实现生命周期处理创建卡片、更新卡片以及删除卡片等请求,提供相应的卡片服务。
- 卡片提供方实例管理模块:由卡片提供方开发者实现,负责对卡片管理服务分配的卡片实例进行持久化管理。
- 通信适配层:由OpenHarmony SDK提供,负责与卡片管理服务通信,用于将卡片的更新数据主动推送到卡片管理服务。
说明: 实际开发时只需要作为卡片提供方进行卡片内容的开发,卡片使用方和卡片管理服务由系统自动处理。
接口说明
FormExtensionAbility类拥有如下API接口,具体的API介绍详见接口文档。
formProvider类有如下API接口,具体的API介绍详见接口文档。
formBindingData类有如下API接口,具体的API介绍详见接口文档。
开发步骤
Stage卡片开发,即基于Stage模型的卡片提供方开发,主要涉及如下关键步骤:
- 创建卡片FormExtensionAbility:卡片生命周期回调函数FormExtensionAbility开发。
- 配置卡片配置文件:配置应用配置文件module.json5和profile配置文件。
- 卡片信息的持久化:对卡片信息进行持久化管理。
- 卡片数据交互:通过updateForm更新卡片显示的信息。
- 开发卡片页面:使用HML+CSS+JSON开发JS卡片页面。
- 开发卡片事件:为卡片添加router事件和message事件。
创建卡片FormExtensionAbility
创建Stage模型的卡片,需实现FormExtensionAbility生命周期接口。先参考DevEco Studio服务卡片开发指南生成服务卡片模板。
1.在EntryFormAbility.ets中,导入相关模块。
import type Base from '@ohos.base';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formInfo from '@ohos.app.form.formInfo';
import formProvider from '@ohos.app.form.formProvider';
import hilog from '@ohos.hilog';
import type Want from '@ohos.app.ability.Want';
const TAG: string = 'JsCardFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;
2.在EntryFormAbility.ets中,实现FormExtension生命周期接口。
export default class JsCardFormAbility extends FormExtensionAbility {
onAddForm(want: Want): formBindingData.FormBindingData {
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onAddForm');
// 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
let obj: Record<string, string> = {
'title': 'titleOnCreate',
'detail': 'detailOnCreate'
};
let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
return formData;
}
onCastToNormalForm(formId: string): void {
// 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onCastToNormalForm');
}
onUpdateForm(formId: string): void {
// 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onUpdateForm');
let obj: Record<string, string> = {
'title': 'titleOnUpdate',
'detail': 'detailOnUpdate'
};
let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
formProvider.updateForm(formId, formData).catch((error: Base.BusinessError) => {
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] updateForm, error:' + JSON.stringify(error));
});
}
onChangeFormVisibility(newStatus: Record<string, number>): void {
// 使用方发起可见或者不可见通知触发,提供方需要做相应的处理,仅系统应用生效
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onChangeFormVisibility');
}
onFormEvent(formId: string, message: string): void {
// 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onFormEvent');
}
onRemoveForm(formId: string): void {
// 删除卡片实例数据
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onRemoveForm');
}
onAcquireFormState(want: Want): formInfo.FormState {
return formInfo.FormState.READY;
}
}
icon-note.gif 说明: FormExtensionAbility不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务。
配置卡片配置文件
1.卡片需要在module.json5配置文件中的extensionAbilities标签下,配置ExtensionAbility相关信息。FormExtensionAbility需要填写metadata元信息标签,其中键名称为固定字符串"ohos.extension.form",资源为卡片的具体配置信息的索引。 配置示例如下:
{
"module": {
...
"extensionAbilities": [
{
"name": "JsCardFormAbility",
"srcEntry": "./ets/jscardformability/JsCardFormAbility.ts",
"description": "$string:JSCardFormAbility_desc",
"label": "$string:JSCardFormAbility_label",
"type": "form",
"metadata": [
{
"name": "ohos.extension.form",
"resource": "$profile:form_jscard_config"
}
]
}
]
}
}
2.卡片的具体配置信息。在上述FormExtensionAbility的元信息("metadata"配置项)中,可以指定卡片具体配置信息的资源索引。例如当resource指定为$profile:form_config时,会使用开发视图的resources/base/profile/目录下的form_config.json作为卡片profile配置文件。内部字段结构说明如下表所示。
表1 卡片profile配置文件
配置示例如下:
{
"forms": [
{
"name": "WidgetJS",
"description": "$string:JSCardEntryAbility_desc",
"src": "./js/WidgetJS/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,因此若卡片提供方支持对卡片数据进行配置,则需要对卡片的业务数据按照卡片ID进行持久化管理,以便在后续获取、更新以及拉起时能获取到正确的卡片业务数据。
import type Base from '@ohos.base';
import type common from '@ohos.app.ability.common';
import dataPreferences from '@ohos.data.preferences';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import hilog from '@ohos.hilog';
import type Want from '@ohos.app.ability.Want';
const TAG: string = 'JsCardFormAbility';
const DATA_STORAGE_PATH: string = '/data/storage/el2/base/haps/form_store';
const DOMAIN_NUMBER: number = 0xFF00;
let storeFormInfo = async (formId: string, formName: string, tempFlag: boolean, context: common.FormExtensionContext): Promise<void> => {
// 此处仅对卡片ID:formId,卡片名:formName和是否为临时卡片:tempFlag进行了持久化
let formInfo: Record<string, string | boolean | number> = {
'formName': formName,
'tempFlag': tempFlag,
'updateCount': 0
};
try {
const storage: dataPreferences.Preferences = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
// put form info
await storage.put(formId, JSON.stringify(formInfo));
hilog.info(DOMAIN_NUMBER, TAG, `[EntryFormAbility] storeFormInfo, put form info successfully, formId: ${formId}`);
await storage.flush();
} catch (err) {
hilog.error(DOMAIN_NUMBER, TAG, `[EntryFormAbility] failed to storeFormInfo, err: ${JSON.stringify(err as Base.BusinessError)}`);
}
}
export default class JsCardFormAbility extends FormExtensionAbility {
onAddForm(want: Want): formBindingData.FormBindingData {
hilog.info(DOMAIN_NUMBER, TAG, '[JsCardFormAbility] onAddForm');
if (want.parameters) {
let formId = JSON.stringify(want.parameters['ohos.extra.param.key.form_identity']);
let formName = JSON.stringify(want.parameters['ohos.extra.param.key.form_name']);
let tempFlag = want.parameters['ohos.extra.param.key.form_temporary'] as boolean;
// 将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用
// 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
storeFormInfo(formId, formName, tempFlag, this.context);
}
let obj: Record<string, string> = {
'title': 'titleOnCreate',
'detail': 'detailOnCreate'
};
let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
return formData;
}
}
且需要适配onRemoveForm卡片删除通知接口,在其中实现卡片实例数据的删除。
import type Base from '@ohos.base';
import type common from '@ohos.app.ability.common';
import dataPreferences from '@ohos.data.preferences';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import hilog from '@ohos.hilog';
const TAG: string = 'JsCardFormAbility';
const DATA_STORAGE_PATH: string = '/data/storage/el2/base/haps/form_store';
const DOMAIN_NUMBER: number = 0xFF00;
let deleteFormInfo = async (formId: string, context: common.FormExtensionContext): Promise<void> => {
try {
const storage: dataPreferences.Preferences = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
// del form info
await storage.delete(formId);
hilog.info(DOMAIN_NUMBER, TAG, `[EntryFormAbility] deleteFormInfo, del form info successfully, formId: ${formId}`);
await storage.flush();
} catch (err) {
hilog.error(DOMAIN_NUMBER, TAG, `[EntryFormAbility] failed to deleteFormInfo, err: ${JSON.stringify(err as Base.BusinessError)}`);
};
};
export default class JsCardFormAbility extends FormExtensionAbility {
onRemoveForm(formId: string): void {
// 删除卡片实例数据
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onRemoveForm');
// 删除之前持久化的卡片实例数据
// 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
deleteFormInfo(formId, this.context);
}
}
具体的持久化方法可以参考轻量级数据存储开发指导。
需要注意的是,卡片使用方在请求卡片时传递给提供方应用的Want数据中存在临时标记字段,表示此次请求的卡片是否为临时卡片:
- 常态卡片:卡片使用方会持久化的卡片;
- 临时卡片:卡片使用方不会持久化的卡片;
由于临时卡片的数据具有非持久化的特殊性,某些场景例如卡片服务框架死亡重启,此时临时卡片数据在卡片管理服务中已经删除,且对应的卡片ID不会通知到提供方,所以卡片提供方需要自己负责清理长时间未删除的临时卡片数据。同时对应的卡片使用方可能会将之前请求的临时卡片转换为常态卡片。如果转换成功,卡片提供方也需要对对应的临时卡片ID进行处理,把卡片提供方记录的临时卡片数据转换为常态卡片数据,防止提供方在清理长时间未删除的临时卡片时,把已经转换为常态卡片的临时卡片信息删除,导致卡片信息丢失。
卡片数据交互
当卡片应用需要更新数据时(如触发了定时更新或定点更新),卡片应用获取最新数据,并调用updateForm()接口主动触发卡片的更新。
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formBindingData from '@ohos.app.form.formBindingData';
import formProvider from '@ohos.app.form.formProvider';
import Base from '@ohos.base';
import hilog from '@ohos.hilog';
const TAG: string = 'JsCardFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;
export default class EntryFormAbility extends FormExtensionAbility {
onUpdateForm(formId: string): void {
// 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onUpdateForm');
let obj: Record<string, string> = {
'title': 'titleOnUpdate',
'detail': 'detailOnUpdate'
};
let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
formProvider.updateForm(formId, formData).catch((error: Base.BusinessError) => {
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] updateForm, error:' + JSON.stringify(error));
});
}
}
开发卡片页面
开发者可以使用类Web范式(HML+CSS+JSON)开发JS卡片页面。生成如下卡片页面,可以这样配置卡片页面文件:
- 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"
}
}
}
}
开发卡片事件
卡片支持为组件设置交互事件(action),包括router事件和message事件,其中router事件用于UIAbility跳转,message事件用于卡片开发人员自定义点击事件。
关键步骤说明如下:
1.在HML中为组件设置onclick属性,其值对应到JSON文件的actions字段中。
2.设置router事件:
- action属性值为"router"。
- abilityName为跳转目标的UIAbility名(支持跳转FA模型的PageAbility组件和Stage模型的UIAbility组件),如目前DevEcoStudio创建的Stage模型的UIAbility默认名为EntryAbility。
- params为传递给跳转目标UIAbility的自定义参数,可以按需填写。其值可以在目标UIAbility启动时的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/CardWebImg.png" class="bg-img"></image>
<image src="/common/CardWebImgMatrix.png"
class="bottom-img"/>
</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%;
z-index: 1;
}
.bottom-img {
position: absolute;
width: 150px;
height: 56px;
top: 63%;
background-color: rgba(216, 216, 216, 0.15);
filter: blur(20px);
z-index: 2;
}
.container-inner {
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
height: 100%;
width: 100%;
padding: 12px;
}
.title {
font-family: HarmonyHeiTi-Medium;
font-size: 14px;
color: rgba(255, 255, 255, 0.90);
letter-spacing: 0.6px;
font-weight: 500;
width: 100%;
text-overflow: ellipsis;
max-lines: 1;
}
.detail_text {
ffont-family: HarmonyHeiTi;
font-size: 12px;
color: rgba(255, 255, 255, 0.60);
letter-spacing: 0.51px;
font-weight: 400;
text-overflow: ellipsis;
max-lines: 1;
margin-top: 6px;
width: 100%;
}
- JSON文件
{
"data": {
"title": "TitleDefault",
"detail": "TextDefault"
},
"actions": {
"routerEvent": {
"action": "router",
"abilityName": "JSCardEntryAbility",
"params": {
"info": "router info",
"message": "router message"
}
},
"messageEvent": {
"action": "message",
"params": {
"detail": "message detail"
}
}
}
}
说明:
"data"中JSON Value支持多级嵌套数据,在更新数据时,需要注意携带完整数据。
例如:当前卡片显示07.18日Mr.Zhang的课程信息,示例如下。
"data": {
"Day": "07.18",
"teacher": {
"name": "Mr.Zhang",
"course": "Math"
}
}
当卡片内容需要更新为07.18日Mr.Li的课程信息时,需要传递待更新的完整数据,不能值传递单个数据项,如只传name或只传course,示例如下。
"teacher": {
"name": "Mr.Li",
"course": "English"
}
- 在UIAbility中接收router事件并获取参数
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import Want from '@ohos.app.ability.Want';
import hilog from '@ohos.hilog';
const TAG: string = 'JsCardEntryAbility';
const DOMAIN_NUMBER: number = 0xFF00;
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (want.parameters) {
let params: Record<string, Object> = JSON.parse(JSON.stringify(want.parameters.params));
// 获取router事件中传递的info参数
if (params.info === 'router info') {
// 执行业务逻辑
hilog.info(DOMAIN_NUMBER, TAG, `router info: ${params.info}`);
}
// 获取router事件中传递的message参数
if (params.message === 'router message') {
// 执行业务逻辑
hilog.info(DOMAIN_NUMBER, TAG, `router message: ${params.message}`);
}
}
}
};
- 在FormExtensionAbility中接收message事件并获取参数
import FormExtension from '@ohos.app.form.FormExtensionAbility';
import hilog from '@ohos.hilog';
const TAG: string = 'FormAbility';
const DOMAIN_NUMBER: number = 0xFF00;
export default class FormAbility extends FormExtension {
onFormEvent(formId: string, message: string): void {
// 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onFormEvent');
// 获取message事件中传递的detail参数
let msg: Record<string, string> = JSON.parse(message);
if (msg.detail === 'message detail') {
// 执行业务逻辑
hilog.info(DOMAIN_NUMBER, TAG, 'message info:' + msg.detail);
}
}
};
如果大家还没有掌握鸿蒙,现在想要在最短的时间里吃透它,我这边特意整理了《鸿蒙语法ArkTS、TypeScript、ArkUI、教学视频》以及《鸿蒙生态应用开发白皮书V2.0PDF》《鸿蒙开发学习手册》(共计890页)鸿蒙相关开发资料等…希望对大家有所帮助:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3
鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3
OpenHarmony APP开发教程步骤:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3
鸿蒙生态应用开发白皮书V2.0PDF:https://docs.qq.com/doc/DZVVkRGRUd3pHSnFG
南北双向高工技能基础:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3
应用开发中高级就业技术:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3
全网首发-工业级 南向设备开发就业技术:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3
《鸿蒙开发学习手册》:
如何快速入门:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3
1.基本概念
2.构建第一个ArkTS应用
3.……
开发基础知识:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3
1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……
基于ArkTS 开发:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3
1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……