一、概述
① 基本概念
服務卡片(以下簡稱“卡片”)是 FA 的一種界面展示形式,將 FA 的重要信息或操作前置到卡片,以達到服務直達,減少體驗層級的目的。 卡片常用于嵌入到其他應用(當前只支持系統應用)中作為其界面的一部分顯示,并支持拉起頁面,發送消息等基礎的交互功能。卡片使用方負責顯示卡片。 服務卡片如下圖所示:
卡片使用方:顯示卡片內容的宿主應用,控制卡片在宿主中展示的位置。 卡片管理服務:用于管理系統中所添加卡片的常駐代理服務,包括卡片對象的管理與使用,以及卡片周期性刷新等。 卡片提供方:提供卡片顯示內容的 HarmonyOS 應用或原子化服務,控制卡片的顯示內容、控件布局以及控件點擊事件。 卡片使用方和提供方不要求常駐運行,在需要添加/刪除/請求更新卡片時,卡片管理服務會拉起卡片提供方獲取卡片信息。 卡片提供方控制卡片實際顯示的內容、控件布局以及控件點擊事件。
② 運作機制
卡片管理服務包含以下模塊: 周期性刷新:在卡片添加后,根據卡片的刷新策略啟動定時任務周期性觸發卡片的刷新。 卡片緩存管理:在卡片添加到卡片管理服務后,對卡片的視圖信息進行緩存,以便下次獲取卡片時可以直接返回緩存數據,降低時延。 卡片生命周期管理:對于卡片切換到后臺或者被遮擋時,暫停卡片的刷新;以及卡片的升級/卸載場景下對卡片數據的更新和清理。 卡片使用方對象管理:對卡片使用方的 RPC 對象進行管理,用于使用方請求進行校驗以及對卡片更新后的回調處理。 通信適配層:負責與卡片使用方和提供方進行 RPC 通信。 卡片提供方包含以下模塊: 卡片服務:由卡片提供方開發者實現,開發者實現 onCreateForm、onUpdateForm 和 onDeleteForm 處理創建卡片、更新卡片以及刪除卡片等請求,提供相應的卡片服務。 卡片提供方實例管理模塊:由卡片提供方開發者實現,負責對卡片管理服務分配的卡片實例進行持久化管理。 通信適配層:由 HarmonyOS SDK 提供,負責與卡片管理服務通信,用于將卡片的更新數據主動推送到卡片管理服務。
二、API 說明
類名接口名描述 AbilityProviderFormInfo onCreateForm(Intent intent)卡片提供方接收創建卡片通知接口 void onUpdateForm(long formId)卡片提供方接收更新卡片通知接口 void onDeleteForm(long formId)卡片提供方接收刪除卡片通知接口 void onTriggerFormEvent(long formId, String message)卡片提供方處理卡片事件接口(JS卡片使用) boolean updateForm(long formId, ComponentProvider component)卡片提供方主動更新卡片(Java卡片使用) boolean updateForm(long formId, FormBindingData formBindingData)卡片提供方主動更新卡片(JS卡片使用),僅更新formBindingData中攜帶的信息,卡片中其余信息保持不變 void onCastTempForm(long formId)卡片提供方接收臨時卡片轉常態卡片通知 void onEventNotify(Map < Long, Integer > formEvents) 卡片提供方接收到事件通知,其中Ability.FORM_VISIBLE表示卡片可見通知, Ability.FORM_INVISIBLE表示卡片不可見通知 FormState onAcquireFormState(Intent intent)卡片提供方接收查詢卡片狀態通知接口,默認返回卡片初始狀態 ProviderFormInfoProviderFormInfo(int resId, Context context)Java卡片返回對象構造函數 ProviderFormInfo()JS卡片返回對象構造函數 void mergeActions(ComponentProvider componentProviderActions)在提供方側調用該接口,將開發者在ComponentProvider中設置的actions配置數據合并到當前對象中 void setJsBindingData(FormBindingData data)設置JS卡片的內容信息(JS卡片使用)
onEventNotify 僅系統應用才會回調,其他接口回調時機如下圖:
卡片管理服務不負責保持卡片的活躍狀態(設置了定時更新的除外),當使用方作出相應的請求時,管理服務會拉起提供方并回調相應接口。
三、Java 卡片與 JS 卡片選型
場景Java卡片JS卡片支持的版本 實時刷新(類似時鐘) Java使用ComponentProvider做實時刷新代價比較大 JS可以做到端側刷新,但是需要定制化組件 HarmonyOS 2.0及以上 開發方式 Java UI在卡片提供方需要同時對數據和組件進行處理,生成ComponentProvider遠端渲染 JS卡片在使用方加載渲染,提供方只要處理數據、組件和邏輯分離 HarmonyOS 2.0及以上 組件支持 Text、Image、DirectionalLayout、PositionLayout、DependentLayout div、list、list-item、swiper、stack、image、text、span、progress、button(定制:chart 、clock、calendar) HarmonyOS 2.0及以上 卡片內動效 不支持 暫不開放 HarmonyOS 2.0及以上 陰影模糊 不支持 支持 HarmonyOS 2.0及以上 動態適應布局 不支持 支持 HarmonyOS 2.0及以上 自定義卡片跳轉頁面 不支持 支持 HarmonyOS 2.0及以上
綜上所述,JS 卡片比 Java 卡片支持的控件和能力都更豐富: Java 卡片:適合作為一個直達入口,沒有復雜的頁面和事件。 對于同一個 Page ability,在 config.json 中最多支持配置 16 張卡片。
三、創建服務卡片
① 卡片目錄結構
JS 服務卡片(entry/src/main/js/Component)的典型開發目錄結構如下:
目錄結構中文件分類如下: .hml 結尾的 HML 模板文件,這個文件用來描述卡片頁面的模板布局結構; .css 結尾的 CSS 樣式文件,這個文件用于描述頁面樣式; .json 結尾的 JSON 文件,這個文件用于配置卡片中使用的變量 action 事件。 各個文件夾的作用: common 目錄用于存放公共資源文件,比如:圖片資源; resources 目錄用于存放資源配置文件,比如:多分辨率加載配置文件; i18n 目錄用于配置不同語言場景資源內容,比如應用文本詞條,圖片路徑等資源。
② 卡片創建
對于創建新工程,可以在工程向導中勾選“Show in Service Center”,該參數表示是否在服務中心露出。如果 Project Type 為 Service,則會同步創建一個 22 的服務卡片模板,同時還會創建入口卡片;如果 Project Type 為 Application,則只會創建一個 22 的服務卡片模板:
卡片創建完成后,會在工程目錄下生成 EntryCard 目錄:
在該目錄下,每個擁有 EntryCard 的模塊,都會生成一個和模塊名相同的文件夾,同時還會默認生成一張 2x2 的快照型 EntryCard 圖片(png 格式)。 可以將其替換為提前設計好的 2x2 快照圖:將新的快照圖拷貝到上圖目錄下,刪除默認圖片,新圖片命名遵循格式“卡片名稱-2x2.png”。 在已有工程中添加新模塊,也可以添加服務卡片和 EntryCard,只需在創建模塊時,勾選“Show in Service Center”即可。創建出來的服務卡片和 EntryCard,同創建新工程生成的一致。 在已有工程中,只添加 EntryCard,只能通過手工方式,按照上圖中的 EntryCard 目錄創建對應的文件夾和圖片。 在已有工程中,新添加服務卡片,可以通過如下方法進行創建: 打開一個工程,創建服務卡片模板,創建方法包括如下兩種方式: 選擇模塊(如entry模塊)下的任意文件,點擊菜單欄 File > New > Service Widget創建服務卡片; 選擇模塊(如entry模塊)下的任意文件,點擊右鍵 > New > Service Widget創建服務卡片。 在 Choose a template for your service widget 界面中,選擇需要創建的卡片模板,點擊 Next:
在 Configure Your Service Widget 界面中,配置卡片的基本信息,包括: Service Widget Name:卡片的名稱,在同一個 FA 中,卡片名稱不能重復,且只能包含數字、字母和下劃線。 Select Ability/New Ability:選擇一個掛靠服務卡片的 Page Ability,或者創建一個新的 Page Ability; JS Component Name:Type 選擇 JS 時需要設置卡片的 JS Component 名稱; Support Dimensions:選擇卡片的規格,部分卡片支持同時設置多種規格;
點擊 Finish 完成卡片的創建。創建完成后,工具會自動創建出服務卡片的布局文件,并在 config.json 文件中寫入服務卡片的屬性字段:
卡片創建完成后,請根據 Java 卡片開發或 JS 卡片開發,完成服務卡片的開發。
四、JS 卡片開發
① 使用 hml+css+json 開發 JS 卡片頁面
使用 DevEco Studio 創建卡片工程 創建成功后,在 config.json 的 module 中會生成 js 模塊,用于對應卡片的 js 相關資源,配置示例如下:
"js" : [ { "name" : "card" , "pages" : [ "pages/index/index" ] , "window" : { "designWidth" : 720 , "autoDesignWidth" : true
} , "type" : "form" } ]
config.json 文件“abilities”配置 forms 模塊細節如下:
"forms" : [ { "name" : "Form_Js" , "description" : "form_description" , "type" : "JS" , "jsComponentName" : "card" , "formConfigAbility" : "ability
: "colorMode" : "auto" , "isDefault" : true
, "updateEnabled" : true
, "scheduledUpdateTime" : "10:30" , "updateDuration" : 1 , "defaultDimension" : "2*2" , "supportDimensions" : [ "2*2" , "2*4" , "4*4" ] , "metaData" : { "customizeData" : [ { "name" : "originWidgetName" , "value" : "com.huawei.weather.testWidget" } ] } } ]
“js”模塊中的 name 字段要與“forms”模塊中的 jsComponentName 字段的值一致,為 js 資源的實例名。 “forms”模塊中的 name 為卡片名,即在 onCreateForm 中根據 AbilitySlice.PARAM_FORM_NAME_KEY 可取到的值。 卡片的 Ability 中還需要配置"visible": true 和"formsEnabled": true。 defaultDimension 是默認規格,必須設置。
屬性名稱子屬性名稱含義數據類型是否可缺省 name-表示卡片的類名,字符串最大長度為127字節字符串否 description-表示卡片的描述,取值可以是描述性內容,也可以是對描述性內容的資源索引,以支持多語言。字符串最大長度為255字節字符串可缺省,缺省為空 isDefault-表示該卡片是否為默認卡片,每個Ability有且只有一個默認卡片 true:默認卡片 false:非默認卡片布爾值否 type-表示卡片的類型,取值范圍如下: Java:Java卡片 JS:JS卡片字符串否 colorMode-表示卡片的主題樣式,取值范圍如下: auto:自適應 dark:深色主題 light:淺色主題字符串可缺省,缺省值為“auto” supportDimensions-表示卡片支持的外觀規格,取值范圍: 1*2:表示1行2列的二宮格 2*2:表示2行2列的四宮格 2*4:表示2行4列的八宮格 4*4:表示4行4列的十六宮格字符串數組否 defaultDimension-表示卡片的默認外觀規格,取值必須在該卡片supportDimensions配置的列表中字符串否 landscapeLayouts-表示卡片外觀規格對應的橫向布局文件,與supportDimensions中的規格一一對應,僅當卡片類型為Java卡片時,需要配置該標簽字符串數組否 portraitLayouts-表示卡片外觀規格對應的豎向布局文件,與supportDimensions中的規格一一對應,僅當卡片類型為Java卡片時,需要配置該標簽字符串數組否 updateEnabled-表示卡片是否支持周期性刷新,取值范圍: true:表示支持周期性刷新,可以在定時刷新(updateDuration)和定點刷新(scheduledUpdateTime)兩種方式任選其一,優先選擇定時刷新 false:表示不支持周期性刷新布爾類型否 scheduledUpdateTime-表示卡片的定點刷新的時刻,采用24小時制,精確到分鐘字符串可缺省,缺省值為“0:0” updateDuration-表示卡片定時刷新的更新周期,單位為30分鐘,取值為自然數: 當取值為0時,表示該參數不生效;當取值為正整數N時,表示刷新周期為30*N分鐘數值可缺省,缺省值為“0” formConfigAbility-表示卡片的配置跳轉鏈接,采用URI格式字符串可缺省,缺省值為空 jsComponentName-表示JS卡片的Component名稱,字符串最大長度為127字節,僅當卡片類型為JS卡片時,需要配置該標簽字符串否 metaData-表示卡片的自定義信息,包含customizeData數組標簽對象可缺省,缺省值為空 customizeData-表示自定義的卡片信息對象數組可缺省,缺省值為空 name表示數據項的鍵名稱,字符串最大長度為255字節字符串可缺省,缺省值為空 value表示數據項的值,字符串最大長度為255字節字符串可缺省,缺省值為空
創建一個 FormAbility,覆寫卡片相關回調函數 onCreateForm(Intent intent) onUpdateForm(long formId) onDeleteForm(long formId) onCastTempForm(long formId) onEventNotify(Map<Long, Integer> formEvents) onTriggerFormEvent(long formId, String message) onAcquireFormState(Intent intent) 當卡片使用方請求獲取卡片時,卡片提供方會被拉起并調用 onCreateForm(Intent intent) 回調,intent 中會帶有卡片 ID、卡片名稱和卡片外觀規格信息,可按需獲取使用。 開發 JS 卡片時,FormAbility 可以繼承 AceAbility 或 Ability,繼承 Ability 時,需在 onStart() 方法中額外設置路由信息。 FormAbility 繼承 AceAbility 的代碼示例:
public class FormAbility extends AceAbility
{ . . . . . . public
static long formId
= - 1 ; @ Overridepublic
void onStart ( Intent intent
) { super . onStart ( intent
) ; } @ Overrideprotected ProviderFormInfo
onCreateForm ( Intent intent
) { long formId
= intent
. getLongParam ( AbilitySlice
. PARAM_FORM_IDENTITY_KEY
, 0 ) ; String formName
= intent
. getStringParam ( AbilitySlice
. PARAM_FORM_NAME_KEY
) ; int specificationId
= intent
. getIntParam ( AbilitySlice
. PARAM_FORM_DIMENSION_KEY
, 0 ) ; boolean tempFlag
= intent
. getBooleanParam ( AbilitySlice
. PARAM_FORM_TEMPORARY_KEY
, false
) ; HiLog
. info ( LABEL_LOG
, "onCreateForm: " + formId
+ " " + formName
+ " " + specificationId
) ; FormBindingData formBindingData
= new
FormBindingData ( "{\"temperature\": \"60°\"}" ) ; ProviderFormInfo formInfo
= new
ProviderFormInfo ( ) ; formInfo
. setJsBindingData ( formBindingData
) ; return formInfo
; } @ Overrideprotected
void onDeleteForm ( long formId
) { super . onDeleteForm ( formId
) ; . . . . . . } @ Overrideprotected
void onUpdateForm ( long formId
) { super . onUpdateForm ( formId
) ; . . . . . . } @ Overrideprotected
void onTriggerFormEvent ( long formId
, String message
) { super . onTriggerFormEvent ( formId
, message
) ; . . . . . . } @ Overrideprotected
void onCastTempForm ( long formId
) { super . onCastTempForm ( formId
) ; . . . . . . } @ Overrideprotected
void onEventNotify ( Map
< Long
, Integer
> formEvents
) { super . onEventNotify ( formEvents
) ; . . . . . . } @ Overrideprotected FormState
onAcquireFormState ( Intent intent
) { ElementName elementName
= intent
. getElement ( ) ; if ( elementName
== null
) { HiLog
. info ( LABEL_LOG
, "onAcquireFormState bundleName and abilityName are not set in intent" ) ; return FormState
. UNKNOWN
; } String bundleName
= elementName
. getBundleName ( ) ; String abilityName
= elementName
. getAbilityName ( ) ; String moduleName
= intent
. getStringParam ( AbilitySlice
. PARAM_MODULE_NAME_KEY
) ; String formName
= intent
. getStringParam ( AbilitySlice
. PARAM_FORM_NAME_KEY
) ; int specificationId
= intent
. getIntParam ( AbilitySlice
. PARAM_FORM_DIMENSION_KEY
, 0 ) ; if ( "form_name2" . equals ( formName
) ) { return FormState
. DEFAULT
; } return FormState
. READY
; } }
FormAbility 繼承 Ability 的代碼示例:
public class FormAbility extends Ability
{ . . . . . . public
static long formId
= - 1 ; @ Overridepublic
void onStart ( Intent intent
) { super . onStart ( intent
) ; super . setMainRoute ( FormAbilitySlice
. class
. getName ( ) ) ; } @ Overrideprotected ProviderFormInfo
onCreateForm ( Intent intent
) { long formId
= intent
. getLongParam ( AbilitySlice
. PARAM_FORM_IDENTITY_KEY
, 0 ) ; String formName
= intent
. getStringParam ( AbilitySlice
. PARAM_FORM_NAME_KEY
) ; int specificationId
= intent
. getIntParam ( AbilitySlice
. PARAM_FORM_DIMENSION_KEY
, 0 ) ; boolean tempFlag
= intent
. getBooleanParam ( AbilitySlice
. PARAM_FORM_TEMPORARY_KEY
, false
) ; HiLog
. info ( LABEL_LOG
, "onCreateForm: " + formId
+ " " + formName
+ " " + specificationId
) ; FormBindingData formBindingData
= new
FormBindingData ( "{\"temperature\": \"60°\"}" ) ; ProviderFormInfo formInfo
= new
ProviderFormInfo ( ) ; formInfo
. setJsBindingData ( formBindingData
) ; return formInfo
; } @ Overrideprotected
void onDeleteForm ( long formId
) { super . onDeleteForm ( formId
) ; . . . . . . } @ Overrideprotected
void onUpdateForm ( long formId
) { super . onUpdateForm ( formId
) ; . . . . . . } @ Overrideprotected
void onTriggerFormEvent ( long formId
, String message
) { super . onTriggerFormEvent ( formId
, message
) ; . . . . . . } @ Overrideprotected
void onCastTempForm ( long formId
) { super . onCastTempForm ( formId
) ; . . . . . . } @ Overrideprotected
void onEventNotify ( Map
< Long
, Integer
> formEvents
) { super . onEventNotify ( formEvents
) ; . . . . . . } @ Overrideprotected FormState
onAcquireFormState ( Intent intent
) { ElementName elementName
= intent
. getElement ( ) ; if ( elementName
== null
) { HiLog
. info ( LABEL_LOG
, "onAcquireFormState bundleName and abilityName are not set in intent" ) ; return FormState
. UNKNOWN
; } String bundleName
= elementName
. getBundleName ( ) ; String abilityName
= elementName
. getAbilityName ( ) ; String moduleName
= intent
. getStringParam ( AbilitySlice
. PARAM_MODULE_NAME_KEY
) ; String formName
= intent
. getStringParam ( AbilitySlice
. PARAM_FORM_NAME_KEY
) ; int specificationId
= intent
. getIntParam ( AbilitySlice
. PARAM_FORM_DIMENSION_KEY
, 0 ) ; if ( "form_name2" . equals ( formName
) ) { return FormState
. DEFAULT
; } return FormState
. READY
; } }
卡片信息持久化 因大部分卡片提供方都不是常駐服務,只有在需要使用時才會被拉起獲取卡片信息,且卡片管理服務支持對卡片進行多實例管理,卡片 ID 對應實例 ID,因此若卡片提供方支持對卡片數據進行配置,則需要對卡片的業務數據按照卡片 ID 進行持久化管理,以便在后續獲取、更新以及拉起時能獲取到正確的卡片業務數據。且需要適配 onDeleteForm(long formId) 卡片刪除通知接口,在其中實現卡片實例數據的刪除。 需要注意的是,卡片使用方在請求卡片時傳遞給提供方應用的 Intent 數據中存在臨時標記字段,表示此次請求的卡片是否為臨時卡片,由于臨時卡片的數據具有非持久化的特殊性,某些場景比如卡片服務框架死亡重啟,此時臨時卡片數據在卡片管理服務中已經刪除,且對應的卡片 ID 不會通知到提供方,所以卡片提供方需要自己負責清理長時間未刪除的臨時卡片數據。同時對應的卡片使用方可能會將之前請求的臨時卡片轉換為常態卡片。如果轉換成功,卡片提供方也需要對對應的臨時卡片 ID 進行處理,把卡片提供方記錄的臨時卡片數據轉換為常態卡片數據,防止提供方在清理長時間未刪除的臨時卡片時,把已經轉換為常態卡片的臨時卡片信息刪除,導致卡片信息丟失。
@ Overrideprotected ProviderFormInfo
onCreateForm ( Intent intent
) { long formId
= intent
. getIntParam ( AbilitySlice
. PARAM_FORM_IDENTITY_KEY
, - 1L ) ; String formName
= params
. getStringParam ( AbilitySlice
. PARAM_FORM_NAME_KEY
) ; int specificationId
= params
. getIntParam ( AbilitySlice
. PARAM_FORM_DIMENSION_KEY
, 0 ) ; boolean tempFlag
= params
. getBooleanParam ( AbilitySlice
. PARAM_FORM_TEMPORARY_KEY
, false
) ; HiLog
. info ( LABEL_LOG
, "onCreateForm: " + formId
+ " " + formName
+ " " + specificationId
) ; . . . . . . . storeFormInfo ( formId
, formName
, specificationId
, formData
) ; . . . . . . HiLog
. info ( LABEL_LOG
, "onCreateForm finish......." ) ; return formInfo
; } @ Overrideprotected
void onDeleteForm ( long formId
) { super . onDeleteForm ( formId
) ; deleteFormInfo ( formId
) ; . . . . . . } @ Overrideprotected
void onCastTempForm ( long formId
) { super . onCastTempForm ( formId
) ; . . . . . . }
卡片數據交互 當卡片應用需要更新數據時(如觸發了定時更新或定點更新),卡片應用獲取最新數據,并調用 updateForm 接口更新卡片。示例如下:
@ Overrideprotected
void onUpdateForm ( long formId
) { super . onUpdateForm ( formId
) ; ZSONObject zsonObject
= new
ZSONObject ( ) ; zsonObject
. put ( "temperature" , "90°" ) ; FormBindingData formBindingData
= new
FormBindingData ( zsonObject
) ; if ( ! updateForm ( formId
, formBindingData
) ) { } }
開發 JS 卡片頁面 JS 卡片頁面與普通 FA 類似通過 hml+css+json 開發,示例如下:
hml:
< div class
= "container" > < stack class
= "stack_container" > < image class
= "img" src
= "common/clouds.png" > < / image
> < div style
= "flex-direction: column;" > < text class
= "txt_city" onclick
= "messageEvent" > { { city
} } < / text
> < text class
= "txt_temperature" onclick
= "routerEvent" > { { temperature
} } < / text
> < / div
> < / stack
> < / div
> css:
. container
{ flex
- direction
: column
; justify
- content
: center
; align
- items
: center
; } . stack_container
{ width
: 100 % ; height
: 100 % ; background
- image
: url ( "/common/weather-background-day.png" ) ; background
- size
: cover
; } . . . json:
{ "data" : { "temperature" : "35°" , "city" : "hangzhou" } , "actions" : { "routerEvent" : { "action" : "router" , "abilityName" : "com.example.myapplication.FormAbility" , "params" : { "message" : "weather" } } , "messageEvent" : { "action" : "message" , "params" : { "message" : "weather update" } } } }
開發 JS 卡片事件和 action JS 卡片支持為組件設置 action,包括 router 事件和 message 事件,其中 router 事件用于應用跳轉,message 事件用于卡片開發人員自定義點擊事件。 在 hml 中為組件設置 onclick 屬性,其值對應到 json 文件的 actions 字段中。 abilityName 為卡片提供方應用的跳轉目標 Ability 名; params中的值按需填寫,其值在使用時通過 intent.getStringParam(“params”) 獲取即可; 若設置 message 事件,則 action 屬性值為"message",params 為 json 格式的值。
hml:
< text class
= "txt_city" onclick
= "messageEvent" > { { city
} } < / text
> < text class
= "txt_temperature" onclick
= "routerEvent" > { { temperature
} } < / text
>
json:
{ "actions" : { "routerEvent" : { "action" : "router" , "abilityName" : "com.example.myapplication.FormAbility" , "params" : { "message" : "weather" } } , "messageEvent" : { "action" : "message" , "params" : { "message" : "test date" , } } } }
當點擊組件觸發 message 事件時,卡片應用的 onTriggerFormEvent 方法被觸發, params 屬性的值將作為參數被傳入,解析使用即可。 message 事件由于是自定義,也可以在 message 事件中實現跳轉到其他 Ability 的能力。但是,在這種情況下,卡片使用方定義的動效是不生效的。宿主側定義的動效僅在 router 事件的跳轉中生效。 如果想要保證動效,使用 routerEvent。routerEvent 配置跳轉鏈接時,只能配置到卡片提供方自己的 ability 中。
② 通過內存圖片方式使用 image 組件
在卡片上如果想要顯示網絡的圖片資源、數據庫中查詢讀取的圖片資源等圖片資源,可以通過 image 組件提供的內存圖片能力。 獲取圖片數據:內存圖片使用 byte[] 格式的圖片數據,可以來自多個途徑:比如網絡的圖片資源、數據庫中查詢讀取的圖片資源、本地圖片打開后獲得的圖片資源等。 調用 FormBindingData 的 addImageData 接口傳入數據: 首先創建一個 ZSONObject,將{imageSrc,memory:// + picName}的鍵值對添加到 ZSONObject 中,其中,imageSrc 是 image 組件 src 屬性關聯的變量(比如在 js 文件中, image 組件的寫法是),picName 是共享內存的圖片名,該命名可以自定義,但是要保證圖片格式的后綴名正確。
ZSONObject zsonObject
= new
ZSONObject ( ) ; zsonObject
. put ( "imageSrc" , "memory
:
使用該 ZSONObject 去創建一個 FormBindingData。 調用 FormBindingData 的 addImageData 接口添加數據:addImageData(“logo.png”, bytes),其中,"logo.png"為 picName,必須和第一步里面添加到 ZSONObject 中的鍵值對的picName一致,否則1中的共享圖片路徑(“memory://logo.png”)將讀取不到這里添加進去的圖片數據。 如果是在卡片的 onCreateForm 生命周期去更新共享內存圖片數據,則只需創建 ProviderFormInfo,然后將 FormBindingData 設置給 ProviderFormInfo中,返回 ProviderFormInfo 即可。 如果是在卡片的其他生命周期去更新共享內存圖片數據,直接調用 updateForm 去更新指定卡片即可,代碼示例在 onCreateForm 中使用。
< ! -- xxx
. hml
-- > < image src
= "{{imageSrc}}" > < / image
>
@ Overrideprotected ProviderFormInfo
onCreateForm ( Intent intent
) { IntentParams params
= intent
. getParams ( ) ; if ( params
== null
) { return null
; } formId
= ( int ) params
. getParam ( AbilitySlice
. PARAM_FORM_ID_KEY
) ; String formName
= ( String
) params
. getParam ( AbilitySlice
. PARAM_FORM_NAME_KEY
) ; int specificationId
= ( int ) params
. getParam ( AbilitySlice
. PARAM_FORM_DIMENSION_KEY
) ; ZSONObject zsonObject
= new
ZSONObject ( ) ; zsonObject
. put ( "imageSrc" , "memory
: FormBindingData formBindingData
= new
FormBindingData ( zsonObject
) ; formBindingData
. addImageData ( "logo.png" , bytes
) ; ProviderFormInfo formInfo
= new
ProviderFormInfo ( ) ; formInfo
. setJsBindingData ( formBindingData
) ; return formInfo
; }
五、Java 卡片開發
① 使用 DevEco Studio 創建卡片工程
卡片應用是一款特殊的元能力服務,其配置文件 config.json 中聲明以下幾項,系統能夠識別該應用為一款卡片應用,并與系統進行綁定。 config.json 文件 “abilities” 配置 forms 模塊細節如下:
"forms" : [ { "name" : "Form_Java" , "description" : "form_description" , "type" : "Java" , "colorMode" : "auto" , "isDefault" : true
, "updateEnabled" : true
, "scheduledUpdateTime" : "10:30" , "updateDuration" : 1 , "defaultDimension" : "2*2" , "formVisibleNotify" : true
, "supportDimensions" : [ "1*2" , "2*2" , "2*4" , "4*4" ] , "landscapeLayouts" : [ "$layout:form_ability_layout_1_2" , "$layout:form_ability_layout_2_2" , "$layout:form_ability_layout_2_4" , "$layout:form_ability_layout_4_4" ] , "portraitLayouts" : [ "$layout:form_ability_layout_1_2" , "$layout:form_ability_layout_2_2" , "$layout:form_ability_layout_2_4" , "$layout:form_ability_layout_4_4" ] , "formConfigAbility" : "ability
: "metaData" : { "customizeData" : [ { "name" : "originWidgetName" , "value" : "com.huawei.weather.testWidget" } ] } } ]
"forms"模塊中的 name 為卡片名,即在 onCreateForm 中根據 AbilitySlice.PARAM_FORM_NAME_KEY 可取到的值。 在卡片所在的"abilities"中還需要配置"visible": true 和"formsEnabled": true。
② 創建一個 FormAbility,覆寫卡片相關回調函數
相關函數: onCreateForm(Intent intent) onUpdateForm(long formId) onDeleteForm(long formId) onCastTempForm(long formId) onEventNotify(Map<Long, Integer> formEvents) onAcquireFormState(Intent intent) 在 onCreateForm(Intent intent) 中,當卡片使用方請求獲取卡片時,卡片提供方會被拉起并調用 onCreateForm(Intent intent) 回調,intent 中會帶有卡片 ID,卡片名稱,臨時卡片標記和卡片外觀規格信息,分別通過 AbilitySlice.PARAM_FORM_IDENTITY_KEY、AbilitySlice.PARAM_FORM_NAME_KEY、AbilitySlice.PARAM_FORM_TEMORARY_KEY和AbilitySlice.PARAM_FORM_DIMENSION_KEY 按需獲取。 提供方可以通過 AbilitySlice.PARAM_FORM_CUSTOMIZE_KEY 獲取卡片使用方設置的自定義數據。
public class FormAbility extends Ability
{ . . . . . . @ Overridepublic
void onStart ( Intent intent
) { super . onStart ( intent
) ; . . . . . . } @ Overrideprotected ProviderFormInfo
onCreateForm ( Intent intent
) { long formId
= intent
. getLongParam ( AbilitySlice
. PARAM_FORM_IDENTITY_KEY
, 0 ) ; String formName
= intent
. getStringParam ( AbilitySlice
. PARAM_FORM_NAME_KEY
) ; int specificationId
= intent
. getIntParam ( AbilitySlice
. PARAM_FORM_DIMENSION_KEY
, 0 ) ; boolean tempFlag
= intent
. getBooleanParam ( AbilitySlice
. PARAM_FORM_TEMPORARY_KEY
, false
) ; IntentParams intentParams
= intent
. getParam ( AbilitySlice
. PARAM_FORM_CUSTOMIZE_KEY
) ; HiLog
. info ( LABEL_LOG
, "onCreateForm: " + formId
+ " " + formName
+ " " + specificationId
) ; ProviderFormInfo formInfo
= new
ProviderFormInfo ( ResourceTable
. Layout_form_ability_layout_2_2
, this
) ; String formData
= getInitFormData ( formName
, specificationId
) ; ComponentProvider componentProvider
= new
ComponentProvider ( ) ; componentProvider
. setText ( ResourceTable
. Id_title
, "formData-" + formData
) ; formInfo
. mergeActions ( componentProvider
) ; . . . . . . HiLog
. info ( LABEL_LOG
, "onCreateForm finish......." ) ; return formInfo
; } @ Overrideprotected
void onDeleteForm ( long formId
) { super . onDeleteForm ( formId
) ; deleteFormInfo ( formId
) ; . . . . . . } @ Overrideprotected
void onUpdateForm ( long formId
) { super . onUpdateForm ( formId
) ; . . . . . . } @ Overrideprotected
void onCastTempForm ( long formId
) { super . onCastTempForm ( formId
) ; . . . . . . } @ Overrideprotected
void onEventNotify ( Map
< Long
, Integer
> formEvents
) { super . onEventNotify ( formEvents
) ; . . . . . . } @ Overrideprotected FormState
onAcquireFormState ( Intent intent
) { ElementName elementName
= intent
. getElement ( ) ; if ( elementName
== null
) { HiLog
. info ( LABEL_LOG
, "onAcquireFormState bundleName and abilityName are not set in intent" ) ; return FormState
. UNKNOWN
; } String bundleName
= elementName
. getBundleName ( ) ; String abilityName
= elementName
. getAbilityName ( ) ; String moduleName
= intent
. getStringParam ( AbilitySlice
. PARAM_MODULE_NAME_KEY
) ; String formName
= intent
. getStringParam ( AbilitySlice
. PARAM_FORM_NAME_KEY
) ; int specificationId
= intent
. getIntParam ( AbilitySlice
. PARAM_FORM_DIMENSION_KEY
, 0 ) ; if ( "form_name2" . equals ( formName
) ) { return FormState
. DEFAULT
; } return FormState
. READY
; } }
③ 卡片信息持久化
因大部分卡片提供方都不是常駐服務,只有在需要使用時才會被拉起獲取卡片信息。且卡片管理服務支持對卡片進行多實例管理,卡片 ID 對應實例 ID,因此若卡片提供方支持對卡片數據進行配置,則需要提供方對卡片的業務數據按照卡片 ID 進行持久化管理,以便在后續獲取、更新以及拉起時能獲取到正確的卡片業務數據。 同時,需要適配 onDeleteForm(int formId) 卡片刪除通知接口,在其中實現卡片實例數據的刪除。和 JS 卡片相同,需要注意卡片使用方在請求卡片時傳遞給提供方應用的 Intent 數據中存在臨時標記字段,表示此次請求的卡片是否為臨時卡片,由于臨時卡片的數據具有非持久化的特殊性,某些場景比如卡片服務框架死亡重啟,此時臨時卡片數據在卡片管理服務中已經刪除,且對應的卡片 ID 不會通知到提供方,所以卡片提供方需要自己負責清理長時間未刪除的臨時卡片數據。同時對應的卡片使用方可能會將之前請求的臨時卡片轉換為常態卡片。如果轉換成功,卡片提供方也需要對對應的臨時卡片 ID 進行處理,把卡片提供方記錄的臨時卡片數據轉換為常態卡片數據,防止提供方在清理長時間未刪除的臨時卡片時,把已經轉換為常態卡片的臨時卡片信息刪除,導致卡片信息丟失。
@ Overrideprotected ProviderFormInfo
onCreateForm ( Intent intent
) { long formId
= intent
. getLongParam ( AbilitySlice
. PARAM_FORM_ID_KEY
, - 1L ) ; String formName
= intent
. getStringParam ( AbilitySlice
. PARAM_FORM_NAME_KEY
) ; int specificationId
= intent
. getIntParam ( AbilitySlice
. PARAM_FORM_DIMENSION_KEY
, 0 ) ; boolean tempFlag
= params
. getBooleanParam ( AbilitySlice
. PARAM_FORM_TEMPORARY_KEY
, false
) ; HiLog
. info ( LABEL_LOG
, "onCreateForm: " + formId
+ " " + formName
+ " " + specificationId
) ; . . . . . . . storeFormInfo ( formId
, formName
, specificationId
, formData
) ; . . . . . . HiLog
. info ( LABEL_LOG
, "onCreateForm finish......." ) ; return formInfo
; } @ Overrideprotected
void onDeleteForm ( long formId
) { super . onDeleteForm ( formId
) ; deleteFormInfo ( formId
) ; . . . . . . } @ Overrideprotected
void onCastTempForm ( long formId
) { super . onCastTempForm ( formId
) ; . . . . . . }
④ 卡片數據更新
當需要卡片提供方更新數據時(如觸發了定時更新、定點更新或者卡片使用方主動請求更新),卡片提供方獲取最新數據,并調用 updateForm 接口更新卡片。示例如下:
@ Overrideprotected
void onUpdateForm ( long formId
) { super . onUpdateForm ( formId
) ; ComponentProvider componentProvider
= new
ComponentProvider ( ResourceTable
. Layout_form_ability_layout_2_2
, this
) ; String formData
= getUpdateFormData ( formId
) ; componentProvider
. setText ( ResourceTable
. Id_title
, "update formData-" + formData
) ; updateForm ( formId
, componentProvider
) ; . . . . . . }
卡片使用方點擊拉起卡片頁面,會在 onStart(Intent intent) 中攜帶 formId(通過 AbilitySlice.PARAM_FORM_IDENTITY_KEY 獲取),若需要在 AbilitySlice 中更新,也可以使用 updateForm 接口進行更新,示例如下:
public class FormAbilitySlice extends AbilitySlice
{ . . . . . . @ Overridepublic
void onStart ( Intent intent
) { super . onStart ( intent
) ; . . . . . . Button button
= new
Button ( this
) ; button
. setText ( "Update form data" ) ; button
. setClickedListener ( component
-> { . . . . . . if ( intent
. hasParameter ( AbilitySlice
. PARAM_FORM_IDENTITY_KEY
) ) { int formId
= intent
. getIntParam ( AbilitySlice
. PARAM_FORM_ID_KEY
, - 1 ) ; ComponentProvider componentProvider
= new
ComponentProvider ( ResourceTable
. Layout_form_ability_layout_2_2
, context
) ; String formData
= getUpdateFormData ( formId
) ; componentProvider
. setText ( ResourceTable
. Id_modifylayout
, "update formData-" + formData
) ; getAbility ( ) . updateForm ( formId
, componentProvider
) ; } } ) ; . . . . . . } }
⑤ Java 卡片控制事件
Java 卡片當前通過 IntentAgent 能力支持對卡片控制設置事件,例如可以使用 START_ABILITY、START_SERVICE 這兩類能力,在點擊整張卡片時,跳轉到提供卡片的 ability。(注:Intent 中支持自定義參數的傳遞,支持的類型有 int/long/String/List) 示例如下:
@ Overrideprotected ProviderFormInfo
onCreateForm ( Intent intent
) { . . . . . . ProviderFormInfo formInfo
= new
ProviderFormInfo ( ResourceTable
. Layout_form_ability_layout_2_2
, this
) ; ComponentProvider componentProvider
= new
ComponentProvider ( ) ; componentProvider
. setIntentAgent ( ResourceTable
. Id_title
, startAbilityIntentAgent ( ) ) ; formInfo
. mergeActions ( componentProvider
) ; . . . . . . return formInfo
; } private IntentAgent
startAbilityIntentAgent ( ) { Intent intent
= new
Intent ( ) ; Operation operation
= new Intent
. OperationBuilder ( ) . withDeviceId ( "" ) . withBundleName ( "com.huawei.ohos.betaapp.link" ) . withAbilityName ( "com.huawei.ohos.betaapp.link.MainAbility" ) . build ( ) ; intent
. setOperation ( operation
) ; List
< Intent
> intentList
= new ArrayList
< > ( ) ; intentList
. add ( intent
) ; List
< Flags
> flags
= new ArrayList
< > ( ) ; flags
. add ( Flags
. UPDATE_PRESENT_FLAG
) ; IntentAgentInfo paramsInfo
= new
IntentAgentInfo ( 200 , IntentAgentConstant
. OperationType
. START_ABILITY
, flags
, intentList
, null
) ; IntentAgent intentAgent
= IntentAgentHelper
. getIntentAgent ( this
, paramsInfo
) ; return intentAgent
; }
⑥ 開發 Java 卡片布局
在使用 DevEco Studio 創建模塊時會生成對應的 Java UI xml 布局文件,需要注意設置ohos:remote=“true”。 以下是天氣卡片 xml 布局示例,供參考:
< ? xml version
= "1.0" encoding
= "utf-8" ? > < DependentLayout xmlns
: ohos
= "http
: ohos
: width
= "match_parent" ohos
: height
= "match_parent" ohos
: id
= "$+id:background" ohos
: orientation
= "vertical" ohos
: background_element
= "$media:weather" ohos
: remote
= "true" > < Textohos
: id
= "$+id:title" ohos
: text
= "天氣1" ohos
: text_size
= "39px" ohos
: text_color
= "#b0c4de" ohos
: top_margin
= "42px" ohos
: left_margin
= "20px" ohos
: width
= "match_content" ohos
: height
= "match_content" / > < Textohos
: id
= "$+id:temperature" ohos
: text
= "35°" ohos
: text_size
= "100px" ohos
: text_color
= "#b0c4de" ohos
: top_margin
= "25px" ohos
: left_margin
= "20px" ohos
: below
= "$id:title" ohos
: width
= "match_content" ohos
: height
= "match_content" / > < Textohos
: id
= "$+id:location" ohos
: text
= "上海" ohos
: text_size
= "39px" ohos
: text_color
= "#b0c4de" ohos
: top_margin
= "24px" ohos
: left_margin
= "20px" ohos
: below
= "$id:temperature" ohos
: width
= "match_content" ohos
: height
= "match_content" / > < Textohos
: id
= "$+id:textView4" ohos
: text
= "9月4號 星期五" ohos
: text_size
= "39px" ohos
: text_color
= "#b0c4de" ohos
: top_margin
= "10px" ohos
: left_margin
= "20px" ohos
: below
= "$id:location" ohos
: width
= "match_content" ohos
: height
= "match_content" / > < Textohos
: id
= "$+id:textView5" ohos
: text
= "多云" ohos
: text_size
= "39px" ohos
: text_color
= "#b0c4de" ohos
: top_margin
= "10px" ohos
: left_margin
= "150px" ohos
: below
= "$id:location" ohos
: end_of
= "$id:textView4" ohos
: align_parent_end
= "true" ohos
: width
= "match_content" ohos
: height
= "match_content" / > < Imageohos
: id
= "$+id:imageView" ohos
: width
= "160px" ohos
: height
= "150px" ohos
: top_margin
= "20px" ohos
: left_margin
= "150px" ohos
: below
= "$id:title" ohos
: end_of
= "$id:temperature" ohos
: image_src
= "$media:clouds" / > < / DependentLayout
>
四、其他可選功能
① 卡片編輯功能
卡片提供方提供一個卡片的編輯頁面,允許從卡片跳轉至編輯頁面。 卡片提供方在配置文件 config.json 中添加參數“formConfigAbility”,配置參數信息的規則如下: 配置該參數的值,導航到編輯頁面的 Page ability,格式如下:ability://單個ability名字 如果不配置“formConfigAbility”參數,則無卡片編輯功能。
② 卡片背景模糊
卡片可以在 config.json 中聲明是否支持背景模糊。聲明方式如下: config.json 的 metaData 中,在 customizeData 中增加一個 name 為 needBlurBackgroundForLauncher 的字符串類型的屬性,value 為“true”表示支持, 否則為不支持。
"forms" : [ { "name" : "Form_JS_DEMO" , "description" : "it is js form" , "type" : "JS" , "jsComponentName" : "card" , "colorMode" : "auto" , "isDefault" : true
, "updateEnabled" : true
, "scheduledUpdateTime" : "10:30" , "defaultDimension" : "2*2" , "supportDimensions" : [ "2*2" , "2*4" ] , "metaData" : { "customizeData" : [ { "name" : "needBlurBackgroundForLauncher" , "value" : "true" } ] } } ]
卡片開發者可以從 onCreateForm 的回調中,獲取是否支持背景模糊:
protected ProviderFormInfo
onCreateForm ( Intent intent
) { . . . . . . IntentParams intentParams
= intent
. getParam ( AbilitySlice
. PARAM_FORM_CUSTOMIZE_KEY
) ; boolean isSupport
= ( boolean
) intentParams
. getParam ( "fa_card_background_blur_support" ) ; . . . . . . }
總結
以上是生活随笔 為你收集整理的HarmonyOS之深入解析服务卡片的使用 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。