HarmonyOS時鐘服務卡片開發(fā)指南
服務卡片(以下簡稱“卡片”)是FA(Feature Ability)的一種界面展示形式,將FA(Feature Ability)的重要信息或操作前置到卡片,以達到服務直達,減少體驗層級目的。
卡片常用于嵌入到其他應用(當前只支持系統(tǒng)應用)中作為其界面的一部分顯示,并支持拉起頁面,發(fā)送消息等基礎的交互功能。
本期Codelab,我們就來為大家介紹如何在HarmonyOS上開發(fā)一個時鐘類服務卡片應用,該卡片開發(fā)使用Java開發(fā)語言,包含2×2、2×4兩種布局的顯示形態(tài),我們先來看看它的顯示效果。
下面我們將集中于卡片應用的創(chuàng)建、更新和刪除,卡片數(shù)據(jù)庫(用于存儲卡片信息)的創(chuàng)建和使用以及卡片數(shù)據(jù)服務的使用和更新。
在開始敲代碼之前,開發(fā)者們需要先下載安裝Huawei DevEco Studio,可參照官網(wǎng)的指南進行操作:
● Huawei DevEco Studio安裝指南:
https://developer.harmonyos.com/cn/docs/documentation/docguides/software_install0000001053582415
本Codelab主要在entry\src\main\java\目錄下完成配置聲明、頁面布局、功能邏輯代碼實現(xiàn),整個工程的代碼結構如下:

● database文件夾
開發(fā)者需自行創(chuàng)建的文件夾,包含兩個java文件——Form和FormDatabase。其中Form為卡片對象,用于存儲卡片id,卡片名稱以及卡片規(guī)格。FormDatabase為卡片數(shù)據(jù)庫對象,用于創(chuàng)建卡片數(shù)據(jù)庫。
● slice文件夾
僅包含ClockCardSlice文件,為應用主頁面。
● utils文件夾
開發(fā)者需自行創(chuàng)建的文件夾,用于存放各類封裝好的工具,在本Codelab中,包含ComponentProviderUtils、DatabaseUtils、DateUtils和LogUtils。其中,ComponentProviderUtils提供獲取ComponentProvider對象的方法,用于更新卡片;DatabaseUtils提供對數(shù)據(jù)庫相關操作的方法;DateUtils提供日期相關操作的方法;LogUtils封裝日志工具類。
● MainAbility
主程序入口,由DevEco Studio生成,用于重寫創(chuàng)建、刪除卡片等方法。
● MyApplication
DevEco Studio生成,無需做任何變更。
● TimerAbility
一種Service Ability,需要開發(fā)者需自行創(chuàng)建,用于時鐘更新。
● layout文件夾
頁面布局文件夾,由于時鐘卡片Codelab涉及兩個尺寸:2×2和2×4,因此需要新建兩個.xml文件用于頁面布局。
● config.json
配置文件,用于卡片和Service Ability的聲明。
了解完工程代碼結構,下面讓我們來對重點步驟一一講解。
一、配置文件
在卡片應用開始開發(fā)前,我們需要在其配置文件config.json中進行以下幾項聲明,使系統(tǒng)能夠識別該應用為一款卡片應用,并使之與系統(tǒng)進行綁定。
在卡片所在的"abilities"中需要配置“formsEnabled": true和"visible": true,使之能被識別為卡片。同時需要配置forms模塊的細節(jié),代碼如下:
- "abilities": [
- {
- ....
- "formsEnabled": true,//表示該Ability支持服務卡片顯示
- "visible": true,
- "forms": [
- {
- "landscapeLayouts": [
- "$layout:form_image_with_info_date_card_2_2",
- "$layout:form_image_with_info_date_card_2_4"
- ], //表示卡片外觀規(guī)格對應的橫向布局文件,僅當卡片類型為Java卡片時,需要配置該標簽
- "isDefault": true, //該卡片為默認卡片
- "scheduledUpdateTime": "10:30",
- "defaultDimension": "2*2", //卡片的默認外觀規(guī)格,這里是2*2
- "name": "DateCard", //卡片的類名
- "description": "This is a service widget", //卡片的描述
- "colorMode": "auto",
- "type": "Java", //表示卡片的類型,這是一個java卡片
- "supportDimensions": [
- "2*2",
- "2*4"
- ], //表示卡片支持的外觀規(guī)格,這里是2*2和2*4
- "portraitLayouts": [
- "$layout:form_image_with_info_date_card_2_2",
- "$layout:form_image_with_info_date_card_2_4"
- ], //表示卡片外觀規(guī)格對應的豎向布局文件,僅當卡片類型為Java卡片時,需要配置該標簽
- "updateEnabled": true, //表示支持周期性刷新,可以在定時刷新
- "updateDuration": 1, //表示卡片定時刷新的更新周期,單位為30分鐘,這里為30分鐘刷新一次
- "formVisibleNotify": true
- }
- ]
有關forms模塊相關屬性說明,開發(fā)者可自行參見官網(wǎng)資料。
● Java卡片開發(fā)指導
https://developer.harmonyos.com/cn/docs/documentation/docguides/abilityservicewidgetproviderjava0000001104082220
二、卡片布局
本篇Codelab為卡片應用設計了2×2和2×4兩種布局風格,效果如下圖:


其中,2×2布局分為四行,展示的內容從上到下分別為日期、時間說明內容、時間具體信息、星期,整體由DependentLayout布局內嵌套四個DirectionalLayout構成,每個DirectionalLayout內均使用Text組件進行展示,部分代碼示例如下:
- <?xml version="1.0" encoding="utf-8"?>
- <DependentLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:background_element="#6A9F99"
- ohos:remote="true">
- <!--DirectionalLayout布局-->
- <DirectionalLayout
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:orientation="vertical">
- <!--日期Text組件-->
- <Text...>
- </DirectionalLayout>
- <!--DirectionalLayout布局-->
- <DirectionalLayout
- ohos:id="$+id:title"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:alignment="horizontal_center"
- ohos:orientation="horizontal"
- ohos:top_margin="35fp">
- <!--時間說明內容(HOUR、MIN、SEC)Text組件-->
- <Text...>
- <Text...>
- <Text...>
- </DirectionalLayout>
- <!--DirectionalLayout布局-->
- <DirectionalLayout
- ohos:id="$+id:time"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:alignment="horizontal_center"
- ohos:below="$id:title"
- ohos:orientation="horizontal"
- ohos:top_margin="0.5fp">
- <!--時間具體信息Text組件-->
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- </DirectionalLayout>
- <!--DirectionalLayout布局-->
- <DirectionalLayout
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:alignment="center"
- ohos:below="$id:time"
- ohos:margin="20fp"
- ohos:orientation="horizontal">
- <!--星期Text組件-->
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- </DirectionalLayout>
- </DependentLayout>
2×4布局分為三行,將日期與星期合為一行,時間說明內容、時間具體信息各單列一行,整體由DependentLayout布局嵌套一個DependentLayout和兩個DirectionalLayout構成。
其中DependentLayout布局中由一個Text組件和一個DirectionalLayout組成,DirectionalLayout中又嵌套七個Text組件。其余兩個DirectionalLayout內均使用Text組件進行展示,部分代碼示例如下:
- <?xml version="1.0" encoding="utf-8"?>
- <DependentLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:background_element="#6A9F99"
- ohos:remote="true">
- <!--DependentLayout布局-->
- <DependentLayout
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:orientation="horizontal">
- <!--日期Text組件-->
- <Text...>
- <!--DirectionalLayout布局-->
- <DirectionalLayout
- ohos:height="match_content"
- ohos:width="match_content"
- ohos:align_parent_right="true"
- ohos:orientation="horizontal"
- ohos:top_margin="10fp">
- <!--星期Text組件-->
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- </DirectionalLayout>
- </DependentLayout>
- <!--DirectionalLayout布局-->
- <DirectionalLayout
- ohos:id="$+id:title"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:alignment="horizontal_center"
- ohos:orientation="horizontal"
- ohos:top_margin="35fp">
- <!--時間說明內容(HOUR、MIN、SEC)Text組件-->
- <Text...>
- <Text...>
- <Text...>
- </DirectionalLayout>
- <!--DirectionalLayout布局-->
- <DirectionalLayout
- ohos:id="$+id:time"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:alignment="horizontal_center"
- ohos:below="$id:title"
- ohos:orientation="horizontal">
- <!--時間具體信息Text組件-->
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- <Text...>
- </DirectionalLayout>
- </DependentLayout>
三、卡片數(shù)據(jù)庫及卡片數(shù)據(jù)服務的創(chuàng)建
卡片數(shù)據(jù)庫的創(chuàng)建
本篇Codelab使用對象關系映射數(shù)據(jù)庫來對卡片ID,卡片名字等信息進行存儲。因此,我們需要創(chuàng)建一個數(shù)據(jù)庫(FormDatabase)和一張表(Form)。
其中,對象關系映射 (ORM) 數(shù)據(jù)庫類對應于關系數(shù)據(jù)庫,在本Codelab中即FormDatabase.java,用于存儲“Form”表,版本號為 “1”。
在使用 ORM 數(shù)據(jù)庫之前,需要創(chuàng)建一個繼承自OrmDatabase的數(shù)據(jù)庫類,并使用 @Database 對其進行注釋。示例代碼如下:
- @Database(
- entities = {Form.class},
- version = 1)
- public abstract class FormDatabase extends OrmDatabase { }
關于OrmDatabase相關開發(fā)信息,開發(fā)者可以自行參考官網(wǎng)資料。
● OrmDatabase
https://developer.harmonyos.com/cn/docs/documentation/docreferences/ormdatabase0000001054838766
此外,我們定義一個對象關系映射 (ORM) 數(shù)據(jù)庫中的實體類Form.java,對應數(shù)據(jù)庫內的表名為“form”,包含了卡片id(formId),卡片名稱(formName) 和卡片規(guī)格(dimension)三個字段。在ORM數(shù)據(jù)庫中操作實體之前,需要創(chuàng)建一個繼承自OrmObject的實體類,并用@Entity注解。示例代碼如下:
- @Entity(tableName = "form")
- public class Form extends OrmObject {
- @PrimaryKey()
- private Long formId;
- private String formName;
- private Integer dimension;
- public Form(Long formId, String formName, Integer dimension) {
- this.formId = formId;//卡片id
- this.formName = formName;//卡片名稱
- this.dimension = dimension;//卡片規(guī)格
- }
- public Form() { }
- public Integer getDimension() {
- return dimension;
- }
- public void setDimension(Integer dimension) {
- this.dimension = dimension;
- }
- public Long getFormId() {
- return formId;
- }
- public void setFormId(Long formId) {
- this.formId = formId;
- }
- public String getFormName() {
- return formName;
- }
- public void setFormName(String formName) {
- this.formName = formName;
- }
- }
關于OrmObject相關開發(fā)信息,開發(fā)者可以自行參考官網(wǎng)資料。
● OrmObject
https://developer.harmonyos.com/cn/docs/documentation/docreferences/ormobject0000001054120141
卡片數(shù)據(jù)服務的創(chuàng)建
由于我們開發(fā)的時鐘服務卡片需要每隔一秒進行刷新,為了方便處理時鐘卡片刷新的定時任務,我們需要在目錄下右鍵new>Ability>Empty Service Ability,創(chuàng)建了一個名為TimerAbility的Service Ability,作為卡片更新定時器,以每秒一次的頻率更新:
- @Override
- public void onStart(Intent intent) {
- HiLog.info(LABEL_LOG, "TimerAbility::onStart");
- connect = helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class);
- startTimer();
- super.onStart(intent);
- }
- // 卡片更新定時器,每秒更新一次
- private void startTimer() {
- Timer timer = new Timer();
- timer.schedule(
- new TimerTask() {
- @Override
- public void run() {
- updateForms();
- }
- },
- 0,
- SEND_PERIOD);
- }
同時,TimerAbility還承擔著卡片更新的功能,我們將在下面詳細介紹。
四、時鐘卡片應用的創(chuàng)建、更新及刪除
在正式進入時鐘FA卡片的創(chuàng)建、更新及刪除的開發(fā)之前,我們先來了解關于卡片開發(fā)的一些基本概念。
服務卡片整體框架主要包含三部分:卡片使用方、卡片管理服務和卡片提供方。
卡片使用方: 顯示卡片內容的宿主應用,控制卡片在宿主中展示的位置。
卡片管理服務: 用于管理系統(tǒng)中所添加卡片的常駐代理服務,包括卡片對象的管理與使用,以及卡片周期性刷新等。
卡片提供方: 提供卡片顯示內容的HarmonyOS服務/HarmonyOS應用,控制卡片的顯示內容、控件布局以及控件點擊事件。開發(fā)者開發(fā)卡片即為卡片提供方。
說明
卡片使用方和提供方不要求常駐運行,在需要添加/刪除/請求更新卡片時,卡片管理服務會拉起卡片提供方獲取卡片信息。
卡片創(chuàng)建
當卡片使用方請求獲取卡片時,卡片提供方會被拉起并調用onCreateForm回調函數(shù),intent中會帶有卡片ID,卡片名稱和卡片外觀規(guī)格信息,分別通過AbilitySlice.PARAM_FORM_IDENTITY_KEY、AbilitySlice.PARAM_FORM_NAME_KEY和AbilitySlice.PARAM_FORM_DIMENSION_KEY獲取,并根據(jù)卡片的名稱以及外觀規(guī)格獲取對應的xml布局并構造卡片對象,完成卡片信息的創(chuàng)建。
在MainAbility中有如下示例代碼:
- @Override
- protected ProviderFormInfo onCreateForm(Intent intent) {
- if (intent == null) {
- return new ProviderFormInfo();
- }
- // 獲取卡片ID
- formId = INVALID_FORM_ID;
- if (intent.hasParameter(AbilitySlice.PARAM_FORM_IDENTITY_KEY)) {
- formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
- } else {
- return new ProviderFormInfo();
- }
- // 獲取卡片名稱
- String formName = EMPTY_STRING;
- if (intent.hasParameter(AbilitySlice.PARAM_FORM_NAME_KEY)) {
- formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
- }
- // 獲取卡片規(guī)格
- int dimension = DEFAULT_DIMENSION_2X2;
- if (intent.hasParameter(AbilitySlice.PARAM_FORM_DIMENSION_KEY)) {
- dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
- }
- int layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_2;
- if (dimension == DEFAULT_DIMENSION_2X4) {
- layoutId = ResourceTable.Layout_form_image_with_info_date_card_2_4;
- }
- formInfo = new ProviderFormInfo(layoutId, this);
- // 存儲卡片信息
- Form form = new Form(formId, formName, dimension);
- ComponentProvider componentProvider = ComponentProviderUtils.getComponentProvider(form, this);
- formInfo.mergeActions(componentProvider);
- if (connect == null) {
- connect =
- helper.getOrmContext("FormDatabase", "FormDatabase.db", FormDatabase.class);
- }
- try {
- DatabaseUtils.insertForm(form, connect);
- } catch (Exception e) {
- DatabaseUtils.deleteFormData(form.getFormId(), connect);
- }
- return formInfo;
- }
卡片的更新
服務卡片刷新機制分為兩種,第一種是定時/定點更新,即在config.json中配置了定時/定點更新之后,卡片管理服務會定期拉起服務卡片刷新數(shù)據(jù)。第二種是主動更新,即開發(fā)者根據(jù)需要主動調用updateForm方法更新卡片。在本Codelab中,我們選擇的是第二種方式。
我們在之前創(chuàng)建的TimerAbility.java中調用updateForm方法,從數(shù)據(jù)表Form中獲取卡片信息,更新時分秒:
- private void updateForms() {
- // 從數(shù)據(jù)庫中獲取卡片信息
- OrmPredicates ormPredicates = new OrmPredicates(Form.class);
- List<Form> formList = connect.query(ormPredicates);
- // 更新時分秒
- if (formList.size() > 0) {
- for (Form form : formList) {
- // 遍歷卡片列表更新卡片
- ComponentProvider componentProvider = ComponentProviderUtils.getComponentProvider(form, this);
- try {
- Long updateFormId = form.getFormId();
- updateForm(updateFormId, componentProvider);
- } catch (FormException e) {
- // 刪除不存在的卡片
- DatabaseUtils.deleteFormData(form.getFormId(), connect);
- HiLog.error(LABEL_LOG, "onUpdateForm updateForm error");
- }
- }
- }
- }