介绍
本篇Codelab基于Stage模型实现带有卡片的计步应用,用于介绍卡片的开发及生命周期实现。需要完成以下功能:
- 消息通知栏,通知用户今天所行走步数。
- 元服务卡片,在桌面上添加2x2或2x4规格元服务卡片,能看到步数变化,也能看到当天所行走的进度。
- 关系型数据库,用于查询,添加用户行走的数据。
相关概念
- 消息通知:提供通知管理的能力,包括发布、取消发布通知,创建、获取、移除通知通道,订阅、取消订阅通知,获取通知的使能状态、角标使能状态,获取通知的相关信息等。
- 关系型数据库:关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。
- 元服务卡片开发:卡片是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达、减少体验层级的目的。
- 卡片提供方:显示卡片内容,控制卡片布局以及控件点击事件。
- 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。
- 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。
环境搭建
软件要求
- DevEco Studio版本:DevEco Studio 3.1 Release。
- OpenHarmony SDK版本:API version 9。
硬件要求
- 开发板类型:润和RK3568开发板。
- OpenHarmony系统:3.2 Release。
环境搭建
完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:
- 获取OpenHarmony系统版本:标准系统解决方案(二进制)。以3.2 Release版本为例:
2.搭建烧录环境。
- 完成DevEco Device Tool的安装
- 完成RK3568开发板的烧录
3.搭建开发环境。
- 开始前请参考工具准备,完成DevEco Studio的安装和开发环境配置。
- 开发环境配置完成后,请参考使用工程向导创建工程(模板选择“Empty Ability”)。
- 工程创建完成后,选择使用真机进行调测。
代码结构解读
本篇Codelab只对核心代码进行讲解。
├──entry/src/main/ets // 代码区
│ ├──common
│ │ ├──constants
│ │ │ └──CommonConstants.ets // 常量类
│ │ ├──database
│ │ │ ├──Form.ets // 数据库卡片操作
│ │ │ └──SensorData.ets // 数据库行走步数操作
│ │ └──utils
│ │ ├──ChartDataUtils.ets // 图表数据操作工具类
│ │ ├──DatabaseUtils.ets // 数据库工具类
│ │ ├──DateUtils.ets // 日期工具类
│ │ ├──GlobalContext.ets // 项目工具类
│ │ └──Logger.ets // 日志打印工具类
│ ├──entryability
│ │ └──EntryAbility.ets // 程序入口类
│ ├──entryformability
│ │ └──EntryFormAbility.ets // 卡片创建,更新,删除操作类
│ ├──pages
│ │ └──MainPage.ets // 主界面
│ └──viewmodel
│ ├──ChartPoint.ets // 图表点类
│ ├──ChartValues.ets // 图表值类
│ ├──FormData.ets // 表单数据类
│ └──PointStyle.ets // 图表点样式类
├──entry/src/main/js // js代码区
│ ├──card2x2 // 2x2卡片目录
│ ├──card2x4 // 2x4卡片目录
│ ├──common // 卡片资源目录
│ └──i18n // 卡片国际化目录
└──entry/src/main/resources // 资源文件目录
关系型数据库
元服务卡片需要用数据库保存不同时间、不同卡片的数据,而且在添加多张卡片情况下,需要保持数据同步刷新。因此需要创建两张表,一张是保存卡片信息,另一张是记录当天行走步数。
1.数据库创建使用的SQLite。
// CommonConstants.ets
// 表单SQLite
static readonly CREATE_TABLE_FORM: string = 'CREATE TABLE IF NOT EXISTS Form ' +
'(id INTEGER PRIMARY KEY AUTOINCREMENT, formId TEXT NOT NULL, formName TEXT NOT NULL, dimension INTEGER)';
// 行走步数SQLite
static readonly CREATE_TABLE_SENSOR_DATA: string = 'CREATE TABLE IF NOT EXISTS SensorData ' +
'(id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, stepsValue INTEGER)';
2.在EntryAbility的onCreate方法通过DatabaseUtils.createRdbStore方法创建数据库,并创建相应的表。
// EntryAbility.ets
onCreate(want: Want, param: AbilityConstant.LaunchParam): void {
GlobalContext.getContext().setObject('abilityWant', want);
GlobalContext.getContext().setObject('abilityParam', param);
DatabaseUtils.createRdbStore(this.context).then((rdbStore: Object | undefined) => {
// 添加前三天行走模拟数据
DatabaseUtils.addSimulationData(rdbStore as DataRdb.RdbStore);
}).catch((error: Error) => {
...
});
}
消息通知
需要在MainPage的aboutToAppear调用requestNotification方法申请通知栏权限,效果如图所示:
// MainPage.ets
aboutToAppear() {
// 申请通知栏权限
this.requestNotification();
...
}
requestNotification() {
Notification.requestEnableNotification().then(() => {
...
}).catch((err: Error) => {
...
});
}
通过aboutToAppear的setInterval方法开启定时器,当定时器到10秒后,通过DatabaseUtils.sendNotifications方法发送消息到通知栏。效果如图所示:
// DatabaseUtils.ets
// 发送通知
sendNotifications(stepsValue: string, notificationId: number) {
// 获取当前系统语言
let notificationBarTitle: string;
let Language: string = I18n.System.getSystemLanguage();
// 判断是否为中文
if (Language.match(CommonConstants.CHINESE_LANGUAGE)) {
notificationBarTitle = CommonConstants.NOTIFICATIONS_TITLE_GONE_TODAY_ZH +
stepsValue + CommonConstants.NOTIFICATIONS_TITLE_STEPS_ZH;
} else {
notificationBarTitle = CommonConstants.NOTIFICATIONS_TITLE_GONE_TODAY_EN +
stepsValue + CommonConstants.NOTIFICATIONS_TITLE_STEPS_EN;
}
// 发布NotificationRequest.
Notification.publish({
id: CommonConstants.NOTIFICATIONS_ID,
content: {
contentType: Notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: notificationBarTitle,
text: ''
}
}
}).then(() => {
...
});
}
元服务卡片
使用元服务卡片分为四步:创建、初始化、更新、删除。
创建元服务卡片目录
- 在main目录下,点击鼠标右键 > New > Service Widget。
2.然后选择第一个选项下面带有Hello World字样,点击下一步Next。
3.填写卡片名字(Service widget name)、卡片介绍(Description)、是否开启低代码开发(Enable Super Visual)、开发语言(ArkTS和JS)、支持卡片规格(Support dimension)、关联表单(Ability name)点击Finish完成创建。如需创建多个卡片目录重新按照步骤1执行。
4.创建完卡片后,同级目录出现js目录,然后开发者在js目录下使用hml+css+json开发js卡片页面。
初始化元服务卡片
应用选择添加元服务卡片到桌面后,在EntryFormAbility的onAddForm方法进行卡片初始化操作,效果如图所示:
// EntryFormAbility.ets
onAddForm(want: Want) {
let formId: string = want.parameters !== undefined ?
want.parameters[CommonConstants.FORM_PARAM_IDENTITY_KEY] as string : '';
let formName: string = want.parameters !== undefined ?
want.parameters[CommonConstants.FORM_PARAM_NAME_KEY] as string : '';
let dimensionFlag: number = want.parameters !== undefined ?
want.parameters[CommonConstants.FORM_PARAM_DIMENSION_KEY] as number : 0;
// 创建数据库
DatabaseUtils.createRdbStore(this.context).then((rdbStore: Object | undefined) => {
// 存储卡片信息
let form: Form = new Form();
form.formId = formId;
form.formName = formName;
form.dimension = dimensionFlag;
...
DatabaseUtils.insertForm(form, rdbStore as DataRdb.RdbStore);
getToDaySteps(rdbStore as DataRdb.RdbStore, dimensionFlag, formId);
}).catch((error: Error) => {
...
});
...
// 初始化卡片数据
let formData: FormData = new FormData();
formData.percent = 0;
formData.steps = 0;
return FormBindingData.createFormBindingData(formData);
};
更新元服务卡片
1.初始化加载主页面布局之前,在MainPage的aboutToAppear方法中,调用setInterval方法开启定时器。时间到则先通过DatabaseUtils.insertValues方法把步数插入到数据库,再通过DatabaseUtils.updateForms方法更新卡片步数。
// MainPage.ets
aboutToAppear() {
...
DatabaseUtils.getSensorData(rdbStoreValue, DateUtils.getDate(0))
.then((sensorData: SensorData) => {
if (sensorData) {
this.stepsValue = sensorData.stepsValue;
}
// 开启定时器
this.intervalId = setInterval(() => {
...
DatabaseUtils.insertValues(this.stepsValue, rdbStoreValue);
DatabaseUtils.updateForms(this.stepsValue, rdbStoreValue);
}, CommonConstants.INTERVAL_DELAY_TIME);
...
});
}
// DatabaseUtils.ets
updateForms(stepValue: number, rdbStore: DataRdb.RdbStore) {
let predicates: DataRdb.RdbPredicates =
new DataRdb.RdbPredicates(CommonConstants.TABLE_FORM);
// 查询卡片
rdbStore.query(predicates).then((resultSet: DataRdb.ResultSet) => {
...
// 查询第一行
resultSet.goToFirstRow();
do {
let formId: string = resultSet.getString(resultSet.getColumnIndex(CommonConstants.FIELD_FORM_ID));
let dimension: number = resultSet.getLong(resultSet.getColumnIndex(CommonConstants.FIELD_DIMENSION));
ChartDataUtils.getFormData(formId, stepValue, dimension, rdbStore)
.then((formData: FormData) => {
// 更新多张卡片
FormProvider.updateForm(formData.formId, FormBindingData.createFormBindingData(formData))
.catch((error: Error) => {
...
});
}).catch((error: Error) => {
...
});
} while (resultSet.goToNextRow());
resultSet.close();
}).catch((error: Error) => {
...
});
}
2.卡片添加到桌面后,在EntryFormAbility的onAddForm方法中,调用formProvider.setFormNextRefreshTime方法设置倒计时。时间到了则通过updateSensorData方法更新卡片步数。
// EntryFormAbility.ets
onAddForm(want: Want) {
...
// 五分钟倒计时
formProvider.setFormNextRefreshTime(formId, CommonConstants.FIVE_MINUTES, (error, data) => {
...
});
}
onUpdateForm(formId: string) {
// 更新步数
this.updateSensorData();
...
}
updateSensorData() {
DatabaseUtils.createRdbStore(this.context).then((rdbStore: Object | undefined) => {
...
// 获取今天步数
let getSensorData: Promise<SensorData> =
DatabaseUtils.getSensorData(rdbStore as DataRdb.RdbStore, DateUtils.getDate(0));
getSensorData.then((sensorData: SensorData) => {
let stepValue: number = 0;
if (sensorData) {
stepValue = sensorData.stepsValue;
}
// 更新卡片数据
DatabaseUtils.updateForms(stepValue, rdbStore);
}).catch((error: Error) => {
...
});
}).catch((error: Error) => {
...
});
}
3.通过src/main/resources/base/profile/form_config.json配置文件,根据updateDuration或者scheduledUpdateTime字段配置刷新时间。updateDuration优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。当配置的刷新时间到了,系统调用onUpdateForm方法进行更新。
// form_config.json
{
// 卡片的类名
"name": "card2x2",
// 卡片的描述
"description": "This is a service widget.",
// 卡片对应完整路径
"src": "./js/card2x2/pages/index/index",
// 定义与显示窗口相关的配置
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
// 卡片的主题样式
"colorMode": "auto",
// 是否为默认卡片
"isDefault": true,
// 卡片是否支持周期性刷新
"updateEnabled": true,
// 采用24小时制,精确到分钟
"scheduledUpdateTime": "00:00",
// 当取值为0时,表示该参数不生效,当取值为正整数N时,表示刷新周期为30*N分钟。
"updateDuration": 1,
// 卡片默认外观规格
"defaultDimension": "2*2",
// 卡片支持外观规格
"supportDimensions": [
"2*2"
]
}
// EntryFormAbility.ets
onUpdateForm(formId: string) {
// 更新步数
updateSensorData();
...
}
删除元服务卡片
当用户需要删除元服务卡片时,可以在EntryFormAbility的onRemoveForm方法中,通过DatabaseUtils.deleteFormData方法删除数据库中对应的卡片信息。
// EntryFormAbility.ets
onRemoveForm(formId: string) {
DatabaseUtils.createRdbStore(this.context).then((rdbStore: Object | undefined) => {
...
// 删除数据库中对应的卡片信息
DatabaseUtils.deleteFormData(formId, rdbStore as DataRdb.RdbStore);
}).catch((error: Error) => {
...
});
}
// DatabaseUtils.ets
deleteFormData(formId: string, rdbStore: DataRdb.RdbStore) {
let predicates: DataRdb.RdbPredicates = new DataRdb.RdbPredicates(CommonConstants.TABLE_FORM);
predicates.equalTo(CommonConstants.FIELD_FORM_ID, formId);
rdbStore.delete(predicates).catch((error: Error) => {
...
});
}
总结
您已经完成了本次Codelab的学习,并了解到以下知识点:
- 使用notification发布通知。
- 使用关系型数据库插入、更新、删除卡片数据。
- 使用FormExtensionAbility创建、更新、删除元服务卡片
为了帮助大家更深入有效的学习到鸿蒙开发知识点,小编特意给大家准备了一份全套最新版的HarmonyOS NEXT学习资源,获取完整版方式请点击→《HarmonyOS教学视频》
HarmonyOS教学视频:语法ArkTS、TypeScript、ArkUI等…视频教程
鸿蒙生态应用开发白皮书V2.0PDF:
获取完整版白皮书方式请点击→《鸿蒙生态应用开发白皮书V2.0PDF》
鸿蒙 (Harmony OS)开发学习手册
一、入门必看
- 应用开发导读(ArkTS)
- .……
二、HarmonyOS 概念
- 系统定义
- 技术架构
- 技术特性
- 系统安全
- …
三、如何快速入门?《鸿蒙基础入门学习指南》
- 基本概念
- 构建第一个ArkTS应用
- .……
四、开发基础知识
- 应用基础知识
- 配置文件
- 应用数据管理
- 应用安全管理
- 应用隐私保护
- 三方应用调用管控机制
- 资源分类与访问
- 学习ArkTS语言
- .……
五、基于ArkTS 开发
- Ability开发
- UI开发
- 公共事件与通知
- 窗口管理
- 媒体
- 安全
- 7.网络与链接
- 电话服务
- 数据管理
- 后台任务(Background Task)管理
- 设备管理
- 设备使用信息统计
- DFX
- 国际化开发
- 折叠屏系列
- .……
更多了解更多鸿蒙开发的相关知识可以参考:《鸿蒙 (Harmony OS)开发学习手册》