HarmonyOS服務卡片開發知識總結
前言
服務卡片的征文活動也已經接近尾聲,在這段時間里,論壇里有許多優秀的服務卡片作品和相關的文章涌現。我拜讀了專欄中幾乎所有的服務卡片的開發分享文章,從每一篇文章中提取并汲取精華,整合到本文中。一些較為顯然的介紹性的內容在本文中將會被省略(如卡片有多少種尺寸之類),但是一些即使在很多文章都已經說明過的,而我又認為比較重要的內容,我仍然會再次記錄下來(例如卡片的框架)。另外,作為學習筆記,我會記錄下一些我在學習種遇到的新的名詞的概念,這些概念可能是大家已經熟知的,這部分大家可以直接忽略;文章的內容整合自論壇種的眾多文章,所以大家可能會在本文中看到自己文章的影子。
服務卡片的框架
服務卡片整體框架主要包含三部分:卡片使用方、卡片管理服務和卡片提供方。其概念分別如下:
● 卡片使用方:顯示卡片內容的宿主應用,控制卡片在宿主中展示的位置,如桌面、服務中心、搜索等。
● 卡片管理服務:用于管理系統中所添加卡片的常駐代理服務,包括卡片對象的管理與使用,以及卡片周期性刷新等。
● 卡片提供方:提供卡片顯示內容的HarmonyOS應用或原子化服務,控制卡片的顯示內容、控件布局以及控件點擊事件。
卡片的運行框架有如下示意圖:

簡明版示意圖
詳細版示意圖
卡片的常用功能
Java卡片與JS卡片功能的對比圖

MainAbility的自動生成函數解析
在新建服務卡片的時候,在MainAbility類中將會生成一些回調方法,具體方法及其回調條件如下圖,在后面的具體的卡片操作中,也會再次聲明所調用的回調方法。

在onCreatForm(Intent)方法中,卡片提供方被拉起后intent會攜帶卡片相關信息,具體如下:

服務卡片生命周期回調函數時序如下:

配置卡片編輯功能
有些服務卡片需要具備可編輯能力,如天氣App需要編輯所在城市。具體實現方法如下:在config.json中,對某一個form的配置增加formConfigAbility的屬性配置,可實現編輯功能。formConfigAbility的值是一個url格式的Ability名稱。若不配置formConfigAbility,則不顯示編輯菜單。示例代碼如下:(摘抄自[一文看懂HarmonyOS服務卡片運行原理和開發方法])(https://harmonyos.oss-cn-beijing.aliyuncs.com/images/202106/830a107135e5fa06fa1714ebaa9fa523833da2.jpg?x-oss-process=image/resize,w_1080,h_478)

卡片的定點/定時刷新:將回調updateForm()方法
運作機制示意圖如下:

注意:定時刷新和定點刷新都配置的情況下,定時刷新優先。
JS卡片的定點更新步驟:
1.關閉定時更新:updateDuration”修改為0,以關閉定時刷新(config.json文件中)
2.設定“scheduledUpdateTime”的時刻
3.在具體的xxxxlmpl中重寫updateFormData()方法
4.把需要刷新的數據存入一個ZSONObject實例中
5.將這個實例封裝在一個FormBindingData的實例bindingData中
6.調用MainAbility的方法updateForm(),并將bindingData作為第二個實參。
JS卡片重寫updateFormData()方法的代碼如下:

JAVA卡片的定點更新步驟:
1.前三步與JS卡片相同
2.構造一個ComponentProvider的實例,用于表示一個Java卡片實例,傳入的第一個實參是根據卡片尺寸得到的布局文件。然后,調用方法setText()修改卡片的標題;最后,調用MainAbility的方法updateForm(),并將componentProvider作為第二個實參。
JAVA卡片重寫updateFormData()方法的代碼如下:

卡片的定時刷新
卡片的定時刷新的最小單位時間是三十分鐘,這就帶來了兩個問題:
1.創建卡片后的最初的三十分鐘是不會進行卡片刷新的,所以需要進行手動的刷新。
2.如果是編寫刷新間隔小于三十分的卡片(如時鐘卡片每一秒刷新一次),那么需要自己重新創建一個timer實例來設定刷新時間。
添加手動刷新:用onCreateForm()調用onUpdateForm(formId)即可,代碼摘抄自亮子力的嗶哩嗶哩卡片
- @Override
- protected ProviderFormInfo onCreateForm(Intent intent) {
- ... ...
- //初始化時先在線更新一下卡片
- onUpdateForm(formId);
- return formController.bindFormData();
- }
重新編寫刷新時間的代碼為代碼摘抄自Codelabs 之 時鐘FA卡片開發樣例
- private void startTimer() {
- Timer timer = new Timer();
- timer.schedule(
- new TimerTask() {
- @Override
- public void run() {
- updateForms();
- }
- },
- 0,
- SEND_PERIOD);
- }
- private void updateForms() {
- // 從數據庫中獲取卡片信息
- HiLog.info(LABEL, "從數據庫中獲取卡片");
- OrmPredicates ormPredicates = new OrmPredicates(Form.class);
- List<Form> formList = connect.query(ormPredicates);
- int layoutId=0;
- // 更新時分秒
- if (formList.size() <= 0) {
- return;
- }
- HiLog.info(LABEL, "遍歷卡片列表,逐個更新");
- for (Form form : formList) {
- // 遍歷卡片列表更新卡片
- ComponentProvider componentProvider = ComponentProviderUtils.getComponentProvider(form, this);
- try {
- Long updateFormId = form.getFormId();
- HiLog.info(LABEL, "updateForm,更新卡片[:"+updateFormId+"] 的數據,并非數據庫操作" );
- //更新卡片數據
- boolean isSucc=updateForm(updateFormId, componentProvider);
- HiLog.info(LABEL, "更新卡片數據完成,"+isSucc);
- } catch (FormException e) {
- // 刪除不存在的卡片
- DatabaseUtils.deleteFormData(form.getFormId(), connect);
- HiLog.error(LABEL, "更新卡片異常,刪除數據庫中不存在的卡片");
- }
- }
- }
JAVA與JS卡片定點定時更新的對比總結
由上可見,兩者的大體思路是相同的:即先在config.json文件中修改配置,然后對具體的xxxxlmpl中的updateFormData()進行重寫。不同的是,JS卡片式通過創建一個ZSONOject實例,然后封裝在FormBindingData的實例中的辦法更新數據;而JAVA卡片則是通過創建ComponentProvider的實例,通過它來更新數據。因而,在調用MainAbility的方法updateForm()的時候,JS卡片的第二個實參是bingding Data,而JAVA卡片是componentProvider。
卡片的跳轉事件:
運作機制示意圖如下:

JS卡片的跳轉事件步驟
1.在對應卡片的index.json文件中添加跳轉配置
相關參數意義具體如下:
2.在index.hml中,在標簽image中添加一個屬性onclick,并將值設置為剛剛在index.json中定義的action的名稱“startSecondAbility”
3.根據key的值“params”獲得一個字符串格式的JSON數據;然后,調用ZSONObject.stringToZSON()將其轉換為一個ZSONObject的實例data;最后,從data中分別獲得”param1”和”param2”這兩個key對應的value。
JAVA卡片的跳轉事件步驟
1.打開MainWidget3Impl,在方法bindFormData()中,當卡片尺寸為2*4時,調用componentProvider的方法setIntentAgent(),傳入的第一個實參是標題在布局文件中的id,第二個實參是用于頁面跳轉的IntentAgent實例。代碼如下所示:
2.定義方法getStartAbilityIntentAgent()的具體實現,代碼如下:
總結
Java卡片要通過IntentAgent來設置事件,相比之下JS似乎顯得要方便一些。
卡片的消息事件
JS卡片的消息事件步驟
1.在對應卡片的index.json文件中添加信息事件配置
相關參數意義具體如下:
2.重寫方法onTriggerFormEvent(),代碼如下:
message是一個字符串格式的JSON數據;然后,調用ZSONObject.stringToZSON()將message轉換為一個ZSONObject的實例data;最后,從data中分別獲得”p1”和”p2”這兩個key對應的value。
服務卡片常用到的數據庫
數據持久化
概念:數據持久化就是將內存中的數據模型轉換為存儲模型,以及將存儲模型轉換為內存中的數據模型的統稱
好處:
1、程序代碼重用性強,即使更換數據庫,只需要更改配置文件,不必重寫程序代碼。
2、業務邏輯代碼可讀性強,在代碼中不會有大量的SQL語言,提高程序的可讀性。
3、持久化技術可以自動優化,以減少對數據庫的訪問量,提高程序運行效率。
數據持久化對象的基本操作有:保存、更新、刪除、查詢等。
輕量級偏好數據庫
輕量級偏好數據庫在服務卡片開發中的使用場景
根據輕量級偏好數據庫的特點,在服務卡片的開發中,涉及到保存網絡上的內容時,可以使用輕量級偏好數據庫來存儲cookie信息。
輕量級偏好數據庫的創建(獲取preference實例)
- context = getContext();
- databaseHelper = new DatabaseHelper(context);
- filename = "preference_db";//文件名,不能為空,也不能包含路徑
- preferences = databaseHelper.getPreferences(filename);
輕量級偏好數據庫的插入,刪除,修改,查詢
插入與修改:
首先獲取指定文件對應的Preferences實例,然后借助Preferences API將數據寫入Preferences實例,通過flush或者flushSync將Preferences實例持久化。其中flush為異步地寫入,flushSync為同步寫入。flush()會立即更改內存中的Preferences對象,但會將更新異步寫入磁盤。flushSync()更改內存中的數據的同時會將數據同步寫入磁盤。由于flushSync()是同步的,建議不要從主線程調用它,以避免界面卡頓。
異步寫入代碼:
- preferences.putInt("number",number);
- preferences.putString("fruit","apple");
- preferences.flush();
同步寫入的代碼:
- preferences.putInt("intKey", 3);
- preferences.putString("StringKey", "String value");
- bool result = preferences.flushSync();
查詢:
- int value = preferences.getInt("intKey", 0);
刪除:
1.刪除preferen單實例
- DatabaseHelper databaseHelper = new DatabaseHelper(context);
- String fileName = "name"; // fileName表示文件名,其取值不能為空,也不能包含路徑。
- databaseHelper.removePreferencesFromCache(fileName);
2.刪除指定文件
- DatabaseHelper databaseHelper = new DatabaseHelper(context);
- String fileName = "name"; // fileName表示文件名,其取值不能為空,也不能包含路徑。
- boolean result = databaseHelper.deletePreferences(fileName);
注冊觀察者
- private class PreferencesObserverImpl implements Preferences.PreferencesObserver {
- @Override
- public void onChange(Preferences preferences, String key) {
- if ("intKey".equals(key)) {
- HiLog.info(LABLE, "Change Received:[key=value]");
- }
- }
- }
- // 向preferences實例注冊觀察者
- PreferencesObserverImpl observer = new PreferencesObserverImpl();
- preferences.registerObserver(observer);
- // 修改數據
- preferences.putInt("intKey", 3);
- preferences.flush();
- // 修改數據后,observer的onChange方法會被回調
- // 向preferences實例注銷觀察者
- preferences.unRegisterObserver(observer);
輕量級數據庫在服務卡片中的使用例子:(摘抄自亮子力的嗶哩嗶哩卡片
- /**
- * Map[保存]到偏好型數據庫
- * @param preferences 數據庫的Preferences實例
- * @param map 要保存的map
- */
- public static void SaveMap(Preferences preferences,Map<String,String> map){
- // 遍歷map
- for (Map.Entry<String, String> entry : map.entrySet()) {
- HiLog.info(TAG,entry.getKey() + "=" + entry.getValue());
- preferences.putString(entry.getKey(),entry.getValue());//3.將數據寫入Preferences實例,
- }
- preferences.flushSync();//4.通過flush()或者flushSync()將Preferences實例持久化。
- }
- /**
- * 從偏好型數據庫[讀取]Map
- * @param preferences 數據庫的Preferences實例
- * @return 要讀取的map
- */
- public static Map<String,?> GetCookieMap(Preferences preferences){
- Map<String, ?> map = new HashMap<>();
- map = preferences.getAll();//3.讀取數據
- return map;
- }
對象關系映射數據庫
對象關系數據庫在服務卡片中的應用
對象關系數據庫在服務卡片中主要用作存儲卡片的信息,如卡片id,卡片名字,卡片大小等。
對象關系數據庫的創建
1.配置“build.gradle”文件,這里僅展示使用注解處理器的模塊為“com.huawei.ohos.hap”模塊,則需要在模塊的“build.gradle”文件的ohos節點中添加以下配置:
- compileOptions{
- annotationEnabled true
- }
2.構造對象關系映射數據庫:創建數據庫類并配置對應的屬性
- //數據庫有三個表,User,Book, AllDataType三個表,版本號為1
- @Database(entities = {User.class, Book.class, AllDataType.class}, version = 1)
- public abstract class BookStore extends OrmDatabase {
- }
3.構造數據表,即創建數據庫實體類并配置對應的屬性(如對應表的主鍵,外鍵等)。數據表必須與其所在的數據庫在同一個模塊中。
- @Entity(tableName = "user", ignoredColumns = {"ignoredColumn1", "ignoredColumn2"},
- indices = {@Index(value = {"firstName", "lastName"}, name = "name_index", unique = true)})
- public class User extends OrmObject {
- // 此處將userId設為了自增的主鍵。注意只有在數據類型為包裝類型時,自增主鍵才能生效。
- @PrimaryKey(autoGenerate = true)
- private Integer userId;
- private String firstName;
- private String lastName;
- private int age;
- private double balance;
- private int ignoredColumn1;
- private int ignoredColumn2;
- // 需添加各字段的getter和setter方法。
- }
4.使用對象數據操作接口OrmContext創建數據庫。
- // context入參類型為ohos.app.Context,注意不要使用slice.getContext()來獲取context,請直接傳入slice,否則會出現找不到類的報錯。
- DatabaseHelper helper = new DatabaseHelper(this);
- OrmContext context = helper.getOrmContext("BookStore", "BookStore.db", BookStore.class);
5.對象關系映射數據庫的增刪改查
查詢數據:
- OrmPredicates query = context.where(User.class).equalTo("lastName", "San");
- List<User> users = context.query(query);
更新數據:更新數據有兩種方法,這里僅貼出其中一種
- // 更新數據
- OrmPredicates predicates = context.where(User.class);
- predicates.equalTo("age", 29);
- List<User> users = context.query(predicates);
- User user = users.get(0);
- user.setFirstName("Li");
- context.update(user);
- context.flush();
刪除數據:刪除數據也有兩種方法,這里也僅貼出其中一種,另一種方法可以到官方文檔查看
- // 刪除數據
- OrmPredicates predicates = context.where(User.class);
- predicates.equalTo("age", 29);
- List<User> users = context.query(predicates);
- User user = users.get(0);
- context.delete(user);
- context.flush();
6.注冊觀察者
- // 定義一個觀察者類。
- private class CustomedOrmObjectObserver implements OrmObjectObserver {
- @Override
- public void onChange(OrmContext changeContext, AllChangeToTarget subAllChange) {
- // 用戶可以在此處定義觀察者行為
- }
- }
- // 調用registerEntityObserver方法注冊一個觀察者observer。
- CustomedOrmObjectObserver observer = new CustomedOrmObjectObserver();
- context.registerEntityObserver("user", observer);
- // 當以下方法被調用,并flush成功時,觀察者observer的onChange方法會被觸發。其中,方法的入參必須為User類的對象。
- public <T extends OrmObject> boolean insert(T object)
- public <T extends OrmObject> boolean update(T object)
- public <T extends OrmObject> boolean delete(T object)
對象關系映射數據庫在服務卡片中的應用實例
- @Entity(tableName = "form")
- public class Form extends OrmObject {
- @PrimaryKey()
- // 卡片id
- private Long formId;
- // 卡片名稱
- private String formName;
- // 卡片名稱
- private int dimension;
- /**
- * 有參構造
- *
- * @param formId 卡片id
- * @param formName 卡片名
- * @param dimension 卡片規格
- */
- public Form(Long formId, String formName, int dimension) {
- this.formId = formId;
- this.formName = formName;
- this.dimension = dimension;
- }
- protected ProviderFormInfo onCreateForm(Intent intent) {
- HiLog.info(TAG, "onCreateForm");
- long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
- String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
- int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
- HiLog.info(TAG, "onCreateForm: formId=" + formId + ",formName=" + formName);
- FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
- FormController formController = formControllerManager.getController(formId);
- formController = (formController == null) ? formControllerManager.createFormController(formId,
- formName, dimension) : formController;
- if (formController == null) {
- HiLog.error(TAG, "Get null controller. formId: " + formId + ", formName: " + formName);
- return null;
- }
- //初始化時先在線更新一下卡片
- onUpdateForm(formId);
- // TODO 獲取添加到桌面的服務卡片ID
- // 存儲卡片信息。對象關系映射數據庫2.通過對象數據操作接口OrmContext,創建一個別名為“FormDatabase”,數據庫文件名為“FormDatabase.db”的數據庫
- ormContext = databaseHelper.getOrmContext("FormDatabase", "FormDatabase.db", MyOrmDatabase.class);
- // 對象關系映射數據庫3.實例化數據表
- HiLog.info(TAG, "存儲卡片信息: formId=" + formId + ",formName=" + formName + ",dimension=" + dimension);
- Form form = new Form(formId, formName, dimension);
- // 對象關系映射數據庫4.添加方法
- ormContext.insert(form);
- ormContext.flush();
- return formController.bindFormData();
- }
網絡編程的相關知識
這部分在我平時的學習未曾接觸過,在大家眼中很平常的概念,在我這里都是全新的知識,所以會有較多的概念記錄。
WebView組件的使用
WebView提供在應用中集成Web頁面的能力。
服務卡片的開發中一般只需要加載Web頁面,則常用操作如下:
1.配置應用的網絡權限(如未配置網絡權限的話)
- {
- ...
- "module": {
- ...
- "reqPermissions": [
- {
- "name": "ohos.permission.INTERNET"
- }
- ],
- ...
- }
- }
2.使用load方法加載Web頁面
這里僅貼出使用xml方式布局的代碼,使用代碼方式布局可參看官方文檔
- WebView webView = (WebView) findComponentById(ResourceTable.Id_webview);
- webView.getWebConfig().setJavaScriptPermit(true); // 如果網頁需要使用JavaScript,增加此行;如何使用JavaScript下文有詳細介紹
- final String url = EXAMPLE_URL; // EXAMPLE_URL由開發者自定義
- webView.load(url);
什么是JSON文件
JSON的全稱為JavaScript Object Notation,是輕量級的文本數據交換格式,JSON實際上是JavaScript的一個子集。重要的是,許多API請求返回值都是josn格式的結果。
JSON生成Java實體類
網上有很多相關的工具,如何自己編寫我暫且還未學會,就決定先應用現有的工具了
什么是Cookie
由于HTTP是一種無狀態協議,服務器沒有辦法單單從網絡連接上面知道訪問者的身份,因此就使用Cookie作為辨識訪問者的一種標志。
Cookie是一段不超過4KB的小型文本數據,由一個名稱(Name)、一個值(Value)和其它幾個用于控制Cookie有效期、安全性、使用范圍的可選屬性組成。
運作機制如下圖:
客戶端請求服務器,如果服務器需要記錄該用戶狀態,就使用response向客戶端瀏覽器頒發一個Cookie,客戶端瀏覽器會把Cookie保存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給服務器。服務器檢查該Cookie,
以此來辨認用戶狀態。服務器還可以根據需要修改Cookie的內容。
實際就是頒發一個通行證,每人一個,無論誰訪問都必須攜帶自己通行證。這樣服務器就能從通行證上確認客戶身份了。這就是Cookie的工作原理。
cookie 可以讓服務端程序跟蹤每個客戶端的訪問,但是每次客戶端的訪問都必須傳回這些Cookie,如果 Cookie 很多,這無形地增加了客戶端與服務端的數據傳輸量。
Cookie相關方法
卡片開發的基本步驟總結
1.選擇已有的卡片模板,或者自行繪制卡片樣式
2.對卡片進行初始化的設置
3.建立數據庫(對象關系映射數據庫存儲卡片信息,涉及網絡的可以用輕量級數據庫存儲cookie
4.給卡片上面的組件添加響應的事件
5.實現卡片的功能
























