HarmonyOS 實戰——萬字分析并學習 JsFACard 專案
- 專案結構
- 原始碼學習
- config.json
- module
- abilities
- js
- Java 部分
- 類
- 成員變數
- 方法
- JavaScript 部分
- jsmusictemplate
- hml
- css
- json
- 總結
在上一篇中學習原子化服務的文 HarmonyOS 實戰——認識服務卡片及運行第一個服務卡片 的后半部分學習了如何運行官方提供的案例,運行效果如下:
這篇文就官方提供的 JsFACard 原始碼進行學習,JsFACard 的命名是因為這個專案主要是基于 JavaScript 進行實作的 FA(Feature Ability,元服務,代表有界面的 Ability,用于與用戶進行互動) 卡片服務(Card 的由來),
完成了 JsFACard 案例學習應該能夠掌握以下技能:
-
了解原子服務的基礎結構
即 src 的專案結構
-
了解基礎的卡片服務的配置
即通過修改 config.json 完成卡片服務的配置
-
了解卡片服務的結構
-
實作卡片服務的資料交換和狀態更新
通過 json/js 實作狀態管理,以及通過 Java 代碼中的
onTriggerFormEvent方法 實作卡片狀態的更新
專案結構
主要的內容,即 src 目錄下的內容如下:
也就是下面的結構:
|- src
| |- main # 主要內容
| | |- java # java 依舊會別用做為主要的實體管理模塊
| | | |- 存盤其他相關物件的目錄
| | | | |- ...
| | | |- MainAbility # 主程式入口,DevEco Studio生成
| | | |- MyApplication # DevEco Studio生成,不需變更
| | |- js # 主要的呈現部分
| | | |- card # 卡片1號
| | | | |- common # 存放公共資源檔案
| | | | |- i18n # 多語言支持
| | | | |- pages # 存放所有組件頁面
| | | | | |- index # 入口,包含對應檔案
| | | | | | |- index.css
| | | | | | |- index.hml
| | | | | | |- index.json
| | | |- default # 主程式,非 卡片
| | | | |- ... # 結構基本一致,除了沒有 index.json 檔案,取而代之的是 index.js
| | | | |- app.js # 用于全域JavaScript邏輯和應用生命周期管理
| | | |- jscardtemplate # 卡片2號
| | | | |- 結構一樣
| | | |- jsmusictemplate # 卡片3號
| | | | |- 結構一樣
| | |- resources # 共享資源
| | |- config.json # 組態檔
| |- ohosTest # 測驗部分,這里不會贅述
基礎結構相對而言還是比較簡單,理解起來也不是非常的復雜,不過剛開始看的時候不知道還需要寫 java 就有些蒙逼,不過最侄訓是下載了 JsFACard 才算搞明白,原來使用 JavaScript 開發不代表純 JavaScript 開發這個道理,
上文內容所包含的參考資料有:
-
app.js
這篇文章講述了
app.js的用處 -
JS FA 概述
卡片服務始侄訓是依賴于 FA 而進行實作,換言之,不了解 FA 就無法實作卡片服務
-
代碼結構解讀
這個案例是 JS 計步器卡片 這個案例,代碼更加復雜一些,所以剛開始沒有選擇這個案例進行學習
-
檔案組織
服務卡片的檔案組織
原始碼學習
這是官方提供的案例,下載地址在:https://gitee.com/openharmony/app_samples/tree/master/UI/JsFACard,
config.json
完整的組態檔可以在案例中的 JsFACard / entry / src / main / config.json 看到網址在:https://gitee.com/openharmony/app_samples/blob/master/UI/JsFACard/entry/src/main/config.json,
config.json 的大體結構如下:

可以看出來,config.json 有三個最大的,不可預設 的模塊:
-
app
表示應用的全域配置資訊,同一個應用程式中,app 中的資訊必須保持一致,
這里不會贅述,
-
deviceConfig
應用在具體設備上的配置資訊,這里不會贅述,
-
module
這塊是重點,會結合檔案詳細學習一下,
module
這是重點,module 部分管理所有當前 HAP(HarmonyOS Ability Package) 的配置資訊,每個 HAP 是 Ability 的部署包,Ability 為 應用/服務 的基本組成單位,我的理解是某個功能的具體實作,
這里會結合專案結構對 module 中的內容進行分析和學習,
-
package,不可預設
package 是 HAP 的包結構名稱,在應用內應保證唯一性,建議與 HAP 的工程目錄保持一致,
例如說這個專案的 package 值是
ohos.samples.jsfacard,與 HAP 的工程目錄是保持一致的(畢竟官方自己建議這么實施),
-
name,不可預設
HAP 的類名,前綴需要與同級的 package 標簽指定的包名一致,也可采用
.開頭的命名方式,也就是使用絕對定位和相對定位的關系,原本的值使用的是相對定位,也就是采用
.開頭的命名方式:.MainAbility,這種情況下,系統運行時回去尋找 package 下的類名進行打包,本機測驗采用絕對定位的方式,也就是
ohos.samples.jsfacard.MainAbility一樣可以運行, -
mainAbility
表示 HAP 包的入口 ability 名稱,如果存在 page 型別的 ability 就不可預設,
案例情況下,name 和 mainAbility 指向的是同一個類——
ohos.samples.jsfacard.MainAbility, -
deviceType,不可預設
當前 應用/服務 可運行的設備,接受的引數為字串型別,預設的時候只勾選了手機,所以這里的值是
["phone"], -
distro,不可預設
HAP 發布的具體描述,包含的資訊有:
-
deliveryWithInstall,不可預設,建議設定為 true
當前 HAP 是否支持隨應用安裝,
設定 false 就代表了安裝應用不會安裝當前 HAP 的意思?不是很明白這個特性是什么意思,
-
moduleName
模塊名稱,這點抬頭看最上面即可:

值是
entry -
moduleType
表示 HAP 的型別,目前只能在 entry 和 feature 中選擇,
-
installationFree
免安裝特性,JsFACard 默認值是 false,感覺設定為 true 也可以吧,
-
-
abilities,可預設
陣列格式,其中每個元素表示一個提供的服務,整個陣列代表著所有提供的服務,
-
js,可預設
陣列格式,表示基于 JS UI 框架開發的 JS 模塊集合,其中的每個元素代表一個 JS 模塊的資訊,
在 JsFACard 之中,除了 default 是默認程式的 UI 之外,其余每一個 JS 物件所對應的都是一個 forms,也就是卡片服務:

abilities
JsFACard 專案里值包含了一個物件,物件的中的鍵值對包含:
-
skills
表示 Ability 能夠接收的 Intent 的特征,一般使用的時候系統預定義內容,JsFACard 中的配置使用的就是系統預定義內容,具體配置如下:
{ "skills": [ { "entities": ["entity.system.home"], "actions": ["action.system.home"] } ] } -
name
表示 ability 的名稱,
鑒于這只是一個 Demo 專案,并且專案的入口和服務的入口是一樣的,所以這里的值依舊是
.MainAbility -
icon
圖示,注意這里的值使用的是
$media:icon,注意看提示:
icon 接受值的格式就是
$media:some-value這樣一個格式,資源(resources) 下存放資源是有一定程度上的固定格式的,例如說 resources 下存放資源是需要有兩級目錄,一級子目錄為 base 目錄 和 限定詞目錄,二級子目錄為資源目錄,圖解如下:
resources |---base // 默認存在的目錄 | |---element | | |---string.json | |---media | | |---icon.png |---en_GB-vertical-car-mdpi // 限定詞目錄示例,需要 開發者自行創建 | |---element | | |---string.json | |---media | | |---icon.png |---rawfile // 默認存在的目錄因為 base 是系統默認存在的目錄,當資源目錄中沒有與設備狀態匹配的限定詞目錄時,會自動參考該目錄中的資源檔案,以
$media:some-value為例,系統會自動匹配名為some-value的多媒體檔案,如果出現多個名字相同的資源,則會默認匹配第一個資源,更多細節可以參考:資源檔案的分類 中的具體條款,
-
description
特性和 icon 相似,格式依舊是
$string:some-value,并且會自動匹配第一條資料, -
formsEnabled
表示服務是否支持 卡片(forms) 功能,僅是用一 page 類
-
label
特性和 icon 和 description 相似,格式依舊是
$string:some-value,并且會自動匹配第一條資料, -
type
表示服務的型別:
-
page,基于 page 模板開發的 FA,用于提供與用戶互動的能力,
也是這里用到的服務,其余的服務型別不多贅述,
-
…
-
-
forms
服務卡片的屬性,僅在
"formsEnabled": true時才會起效,接受資料為陣列,陣列中的每一個物件就是一個服務卡片的屬性,卡片的屬性就比較直截了當,沒有什么特別難理解的地方:
{ "jsComponentName": "jsmusictemplate", "isDefault": true, "formConfigAbility": "ability://ohos.samples.jsfacard.MainAbility", "scheduledUpdateTime": "10:30", "defaultDimension": "2*4", "name": "jsmusictemplate", "description": "This is a service widget", "colorMode": "auto", "type": "JS", "supportDimensions": ["2*4"], "updateEnabled": true, "updateDuration": 1 } -
launchType
這里的值是
standard,表明服務可以有多個實體,適用于大多數應用場景
jsmusictemplate 的渲染效果如下:
卡片的 UI 暫且不論,上面的資料,如 This is a service widget(forms > description) 和 Js卡片(abilities > label) 均來源于配置,
js
js 接受的也是陣列型別,每個陣列里面是一個物件:
{
"pages": ["pages/index/index"],
"name": "jsmusictemplate",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"type": "form"
}
滾過一遍 module 和 abilities 就差不多知道這些配置都代表什么意思了,
js 陣列中提供的是卡片的 UI 布局,物件中的 name 與 forms 包含物件中的 jsComponentName,
至此,配置內容已經了解的差不多了,更多更具體的內容可以查看 應用組態檔 接下來可以開始著手了解實作的部分,
Java 部分
Java 部分的代碼量不是很多,畢竟這個服務卡片的內容其實不是很多,主要的結構如下:

類
MainAbility 是主程式入口,可以選擇繼承 AceAbility 或 Ability,這里選擇繼承的是 AceAbility,應該是可以方便一些,畢竟根據官方檔案來說,AceAbility 繼承了 Ability:
public class AceAbility
extends Ability
implements IAbilityContinuation
實作 MainAbility 主要是為了對服務的生命周期進行管理,官方提供的生命周期為:

成員變數
在 MainAbility 中宣告的幾個成員變數有:
public class MainAbility extends AceAbility {
private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, MainAbility.class.getName());
private static final String STATUS = "status";
private static final String PLAY = "play";
private static final String PAUSE = "pause";
private static boolean isStatus = true;
}
其中包含:
-
HiLogLabel
日志類的輔助工具,用于定義日志型別、服務域名和標簽
-
STATUS,PLAY,PAUSE
三個是靜態常量,用于定義狀態
-
isStatus
定義當前組件的狀態,結合其他幾個常量,應該是用來控制音樂的播放,
方法
JsFACard 中多載的方法不是很多,大部分都是呼叫 super.method() 去呼叫父類中已經實作的方法,而非多載,直接呼叫父類實作的方法包含:
-
void onStart(Intent intent)
必須呼叫這個函式去設定 UI,在整個服務的生命周期中只能被呼叫一次
-
ProviderFormInfo onCreateForm(Intent intent)
呼叫這個函式會回傳一個
ProviderFormInfo物件,用于在 UI 上顯示基礎的卡片資訊,以及向用戶提供一個卡片服務 -
void onUpdateForm(long formId)
呼叫函式去通知卡片服務提供商去更新特定的卡片
-
void onDeleteForm(long formId)
呼叫函式去通知卡片服務提供商去洗掉特定的卡片
多載的方法有:
-
void onTriggerFormEvent(long formId, String message)
代碼如下:
public class MainAbility extends AceAbility { @Override protected void onTriggerFormEvent(long formId, String message) { super.onTriggerFormEvent(formId, message); ZSONObject zsonObject = new ZSONObject(); // 主要目的就是為了更新 isStatus 的狀態 和更新 zsonObject if (isStatus) { zsonObject.put(STATUS, PAUSE); isStatus = false; } else { zsonObject.put(STATUS, PLAY); isStatus = true; } FormBindingData formBindingData = new FormBindingData(zsonObject); try { updateForm(formId, formBindingData); } catch (FormException e) { HiLog.info(TAG, "onTriggerFormEvent:" + e.getMessage()); } } }這個函式會根據觸發的事件要操作的行為去進行下面的操作:
-
創建新的 ZSONObject 物件
-
將對應的狀態存盤到 ZSONObject 物件中
-
更新成員變數
isStatus -
將 ZSONObject 物件 寫入 FormBindingData 物件 中去
-
呼叫更新卡片的功能去將 FormBindingData 寫入對應的卡片服務中去,從而實作卡片服務
這一步實作了卡片資料的互動,寫入進卡片的資料有兩種:
"status": "play"和"status": "pause",這兩個值在之后的 JS 部分中會有用, -
在 JS 卡片開發指導 中對呼叫的 API 以及對實作有更具體的描寫,
所以說 Ability 到底是不是 Controller 的一種來著,感覺有點像啊……
JavaScript 部分
JavaScript 部分內容分為兩塊:
-
模塊入口
注意,這不是主程式入口,主程式入口依舊是 Java 中的
MainAbility,模塊目錄結構如下:
還是比較直觀的,pages 負責頁面的不同組件,其下 hml 檔案是 HML 的模板檔案,負責框架;css 檔案負責樣式;js 檔案負責行為,
app.js 負責應用級別的生命周期管理,
具體程式內容這里不會詳細學習,簡單的了解一下內容即可,
-
卡片服務應用
這里以
jsmusictemplate為例,主要是因為jsmusictemplate實作的功能比其他兩個卡片服務更多一些,jsmusictemplate的目錄結構如下:
可以看到,卡片服務和 FA 服務的結構是非常相似的,最大的區別在于 pages 下存在一個
.json檔案,而非.js檔案,這大概是因為這個頁面的邏輯比較簡單,不需要其他一些默認值和函式,所以使用
.json檔案 實作會簡單一些,在另一個更加復雜的專案——JS 計步器卡片中,使用的依舊是.js檔案:
jsmusictemplate
這里主要學習的依舊是 jsmusictemplate,先來看看這個頁面長什么樣的:

可以看出頁面被規劃成了 左邊的音樂播放 和 右邊的常用功能 兩個部分,.hml, .css 和 .json 應該就是基于這兩個模塊進行實作的,
hml
鑒于 .hml檔案 是 HTML 的模板檔案,基于之前的分析,實作起來應該是這樣的結構:
|- container
| |- play-music
| |- shortcuts
實作的結構也是這樣的:

-
音樂播放功能
其中,播放音樂的功能使用了
stack標簽 去實作,根據 檔案-stack 上所描述,這個標簽起到的是讓元素堆疊的效果,也就是讓 svg 檔案堆疊在背景圖片上,stack標簽 會讓后面的元素堆疊到前面的元素上,因此結構里第一個元素是背景圖片,第二個元素才是播放的 icon,注意看一下 icon 的實作代碼:
<image src="/common/{{ status }}.svg" onclick="messageEvent" class="status-image" ></image>這里的功能其實與 Java 部分的代碼和 json 功能都有聯動,
{{}}應該是 Mustache Syntax,中間的status屬于變數名,可以獲得status這個變數,回想一下 Java 代碼中會通過updateForm更新的狀態:"status": "play"和"status": "pause",所以這里src的資料有兩種:"/common/play.svg"和"/common/pause.svg",common 檔案夾中的確存在這兩個檔案:
和 
狀態的變更則由
messageEvent進行觸發,這里的引數是由 json 提供的,等到 json 部分再去具體化, -
快捷鍵功能
即右邊的搜索、播放等功能,基本結構如下:
<div class="main-div medium-display-index"> <div class="wrap-div medium-display-index"> <image src="/common/ic_search.svg" class="image-div"></image> <text class="image-text">{{ $t('strings.search') }}</text> </div> <div class="wrap-div medium-display-index"> <image src="/common/ic_favor.svg" class="image-div"></image> <text class="image-text">{{ $t('strings.favor') }}</text> </div> <div class="wrap-div small-display-index"> <image src="/common/ic_ranking.svg" class="image-div"></image> <text class="image-text">{{ $t('strings.ranking') }}</text> </div> <div class="wrap-div small-display-index"> <image src="/common/ic_recommend.svg" class="image-div"></image> <text class="image-text">{{ $t('strings.recommend') }}</text> </div> </div> <div class="main-div small-display-index"> <div class="wrap-div medium-display-index"> <image src="/common/ic_ranking.svg" class="image-div"></image> <text class="image-text">{{ $t('strings.ranking') }}</text> </div> <div class="wrap-div medium-display-index"> <image src="/common/ic_recommend.svg" class="image-div"></image> <text class="image-text">{{ $t('strings.recommend') }}</text> </div> <div class="wrap-div small-display-index"> <image src="/common/ic_favor.svg" class="image-div"></image> <text class="image-text">{{ $t('strings.favor') }}</text> </div> <div class="wrap-div small-display-index"> <image src="/common/ic_search.svg" class="image-div"></image> <text class="image-text">{{ $t('strings.search') }}</text> </div> </div>這里分別使用了兩個 div 去實作不同的功能,而在不同 div 之間,元素的類名是不一樣的,這是通過 CSS 去控制顯示的內容,去進行布局,
只顯示一個 div 和同時顯示兩個 div 的效果如下:
-
保留父元素為
medium-display-index即,保留第一個 div

-
保留父元素為
small-display-index即,保留第二個 div

-
保留兩個元素

-
可以看到元素中都是 small-display-index 的元素被隱藏了,這是由 CSS 控制的,
css
CSS 的大部分內容都是比較常見的,除了 small-display-index 和 medium-display-index 中使用的 display-index 之前是沒有見過的,
display-index 是 原子布局 中的新特性,檔案中的說明是:
該適用于 div 等支持 flex 布局的容器組件中的子組件上,當容器組件在 flex 主軸上尺寸不足以顯示下全部內容時,按照
display-index值從小到大的順序進行隱藏,具有相同display-index值的組件同時隱藏,默認值為Infinity,表示不隱藏,
更具另外一份檔案,也就是 通用樣式 中可以得知,display 的默認值是 flex,所以當空間不夠的時候,small-display-index 的值就會被隱藏掉,
至于官方為什么這么實作,我覺得和多端適配有關系,下面是平板上顯示的效果,能看到和手機上的效果完全不一樣:

這個布局看起來是完全隱藏了 small-display-index,只顯示 medium-display-index 中內容,因為在平板上的高度足夠的關系,所以 medium-display-index 中的 4 個元素可以全都顯示出來,
至于 small-display-index 和 medium-display-index 加起來的寬度,則通過 音樂播放 的界面去控制的,
關于布局這方面真的還需要好好學習一下,
json
json 相對而言是三個部分中最簡單的部分,因為這里沒有什么特別復雜的邏輯,完整的代碼只有 13 行:
{
"data": {
"status": "play"
},
"actions": {
"messageEvent": {
"action": "message",
"params": {
"message": "music change status"
}
}
}
}
可以看到,在結構體中有 data 和 actions 兩大模塊,data 負責的就是資料,它所匯出的資料被 src="/common/{{ status }}.svg" 所獲取;actions 則負責事件,它所匯出的資料被 onclick="messageEvent" 所獲取,結合 Java 中的代碼,音樂播放器中的事件流程就是這樣的:
觸發效果是這樣的:
總結
至此,JsFACard 這個專案就已經掌握得差不多了,下一步學習的目標就打算學習一下另一個更加復雜的案例:JS 計步器卡片,
本文正在參與“有獎征文 | HarmonyOS 征文大賽”活動,活動鏈接為:https://marketing.csdn.net/p/ad3879b53f4b8b31db27382b5fc65bbc
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/291062.html
標籤:其他
上一篇:都說WEB前端飽和了,但我自學找到了11K的作業,就是掌握了這些技術堆疊
下一篇:SSM整合案例
