卡片代碼生成器:json卡片代碼

背景

隨著華為Harmony OS2.0的發布,各大廠商紛紛搶先與華為展開合作。優酷作為國內領先的長視頻在線視聽平台,與華為公司長期以來保持緊密的合作,共同為消費者帶來優質的影音娛樂體驗。因此,優酷技術團隊也在第一時間投入對鴻蒙系統以及鴻蒙開發者SDK的研究。優酷技術團隊經過多輪的頭腦風暴,利用鴻蒙的某些新特性展開鴻蒙應用開發的嘗試。

鴻蒙OS支持應用以Ability為單位進行部署。Ability分為兩種類型:FA(Feature Ability)和PA(Particle Ability)。FA/PA是應用的基本組成單元,能夠實現特定的業務功能。FA有UI界面,而PA無UI界面。

每種類型為開發者提供了不同的模板,以便實現不同的業務功能。

鴻蒙OS的應用軟體包以APP Pack(Application Package)形式發布,它是由一個或多個HAP(HarmonyOS Ability Package)以及描述每個HAP屬性的pack.info組成。HAP是Ability的部署包,HarmonyOS應用代碼圍繞Ability組件展開。

優酷鴻蒙開發實踐 | 鴻蒙卡片開發

鴻蒙工程通過鴻蒙打包工具鏈打包後,其產物格式即為HAP。

當前,包含有鴻蒙FA/PA的優酷鴻蒙版已經在華為鴻蒙應用市場上架,鴻矇混合包在應用市場上會顯示為「含HarmonyOS服務」。如果App是100% Pure鴻蒙App,其Icon右下角會有HMOS字樣。

在手機桌面上的優酷Icon輕輕上滑,會彈出一個鴻蒙卡片,向用戶推薦最近的熱劇,點擊卡片能快速拉起半屏落地頁顯示更多信息,點擊落地頁則跳轉到優酷客戶端的相應落頁面。

點擊卡片上的圖釘按鈕,還可以將這個FA卡片固定在桌面上。

優酷鴻蒙開發實踐 | 鴻蒙卡片開發
優酷鴻蒙開發實踐 | 鴻蒙卡片開發

這個FA是100%利用鴻蒙API編寫的,可以脫離優酷主客獨立運行。由於FA卡片有極其嚴格的體積限制,而使用native的庫體積則會過大。最終,我們的Widget 通過一個Webview,載入JS版本的前端網路庫去請求優酷內部的網路介面,獲取到數據後再使用鴻蒙的Native圖形圖像API去繪製Native界面。

這個桌面Widget與iOS桌面Widget的區別在於,它不依賴於優酷主客即可運作。即使優酷主客不被啟動,卡片的數據也能夠更新。

鴻蒙卡片的開發模式

在鴻蒙系統上,觸摸優酷主客的應用圖標向上滑動,可以喚起優酷的鴻蒙卡片。實現這一點需要卡片的實現代碼與優酷主客做混合打包,一起提交到應用市場。

而如果要實現服務中心免安裝使用,則需要卡片的獨立包總大小要小於10M。這一體積限制使得很多Native 庫都無法引入,否則無法將體積控制在紅線之內。

最終,優酷鴻蒙卡片的代碼放在一個工程中,方便跟優酷主客進行混合打包。同時,優酷鴻蒙卡片的代碼僅依賴極少數的二、三方庫(例如JSON解析、圖片緩存等),以減小體積。

卡片樣式

鴻蒙系統提供4中大小不同的卡片,根據佔用桌面圖標數量的不同,分別是: 1×2、2×2、2×4、4×4。優酷卡片實現了其中兩種: 2×2和2×4,其中2×2的卡片是必選項。

下圖顯示了兩種不同樣式的卡片,以及不同的出現場景。

桌面服務中心發現
優酷鴻蒙開發實踐 | 鴻蒙卡片開發優酷鴻蒙開發實踐 | 鴻蒙卡片開發優酷鴻蒙開發實踐 | 鴻蒙卡片開發

聲明卡片

跟Android的應用微件類似,鴻蒙的卡片也需要在一個配置文件中聲明。在一個鴻蒙應用中,每個模塊都有自己的配置文件,位於該模塊的代碼main目錄下,名字為config.json。

在配置文件中,每個模塊有一個abilities屬性,其值是一個數組,數組的每一個對象都定義了一個Ability。卡片就定義在其中一個Ability中:

{
  ...
        "formsEnabled": true,
        "forms": [
          {
            "landscapeLayouts": [
              "$layout:youku_widget_2_2",
              "$layout:youku_widget_2_4"
            ],
            "isDefault": true,
            "defaultDimension": "2*2",
            "name": "youku_widget",
            "description": "$string:yk_widget_description",
            "colorMode": "auto",
            "type": "Java",
            "supportDimensions": [
              "2*2",
              "2*4"
            ],
            "portraitLayouts": [
              "$layout:youku_widget_2_2",
              "$layout:youku_widget_2_4"
            ],
            "updateEnabled": true,
            "updateDuration": 1
          }
        ],
  ...
}

鴻蒙系統中,卡片用Form來表示。上述聲明中,formsEnabled用於指示這個Ability是用於定義卡片的。forms數組用來定義一系列的卡片。通常多個卡片可以定義在一個數組元素中。其中landscapeLayouts、portraitLayouts、supportDimensions用於定義卡片的布局文件和大小,updateEnabled、updateDuration用於控制卡片的數據更新,updateDuration的單位是半小時。

生命周期

在鴻蒙系統上,卡片的生命周期比普通的Page Ability要簡單很多,只有三個相關的回調:

/**
 * 創建卡片時的回調。
 * 在intent中,存有創建卡片的一些重要參數,可以通過Intent.getXXXParam()方法獲取。
 * AbilitySlice.PARAM_FORM_IDENTITY_KEY: long類型,用於唯一標識一個卡片
 * AbilitySlice.PARAM_FORM_NAME_KEY: String類型,卡片名稱,即在config.json中定義的name屬性
 * AbilitySlice.PARAM_FORM_DIMENSION_KEY: int類型,卡片大小標識,
 * 取值範圍是1-4,分別表示1x2、2x2、2x4、4x4
 */
protected ProviderFormInfo onCreateForm(Intent intent)

/**
 * 更新卡片時的回調。
 * 這裡的formId就是onCreateForm中的AbilitySlice.PARAM_FORM_IDENTITY_KEY參數。
 */
protected void onUpdateForm(long formId)

/**
 * 刪除卡片時的回調。
 * 這裡的formId就是onCreateForm中的AbilitySlice.PARAM_FORM_IDENTITY_KEY參數。
 */
protected void onDeleteForm(long formId)

傳輸卡片內容

卡片的創建和顯示通常由桌面(或者服務中心、搜索)發起,而決定顯示內容的是優酷卡片這個模塊,內容提供方和顯示方不在同一個進程,甚至由不同開發者開發。在Android上也是一樣的情況。

在這種情況下,一般都是內容提供方通過遠程View的方式將內容渲染到內容顯示方的,鴻蒙系統上這個跨進程的數據傳輸行為是由ComponentProvider來實現的。

創建ComponentProvider有兩種方式:

// 第一種: 在onCreateForm()時,先創建一個卡片對應的ProviderFormInfo實例。
// 再通過ProviderFormInfo的實例拿到向它傳輸數據的ComponentProvider。
ProviderFormInfo form = new ProviderFormInfo(layoutId, context);
ComponentProvider cp = form.getComponentProvider();

// 第二種: 在onUpdateForm()時,直接創建出一個ComponentProvider。
ComponentProvider cp = new ComponentProvider(layoutId, context);

需要注意的問題 1

其中設置IntentAgent時需要注意,通常一個布局中會有多個View來覆蓋根布局的矩形區域。如果設置了IntentAgent的View沒有覆蓋滿根布局,則未覆蓋區域被點擊時,系統也會響應點擊,默認調起這個卡片所屬的Ability,傳入的Intent只包含formId。

針對這個默認調起的Ability,一般有兩種方式解決:一是確保設置了IntentAgent的View覆蓋滿根布局;二是Ability提供兜底方案,例如頁面做成透明,並且自動退出。

需要注意的問題 2

在創建IntentAgent時,需要提供一個IntentAgentInfo實例。這個IntentAgentInfo創建時的第一個參數是一個int類型的請求代碼,這個代碼必須保持各個卡片的不同點擊區都不一樣。否則後設置的IntentAgent會覆蓋先前設置的同一請求代碼的IntentAgent。

需要注意的問題 3

如果是跟優酷主客混合打包,卡片的布局文件中,View的id必須跟主客中所有的id不同,否則系統會無法正確更新布局文件中對應的View。

打開中轉頁

由於系統的限制,點擊卡片打開的頁面必須是純鴻蒙應用中的頁面,無法直接打開Android應用頁面。優酷卡片的點擊,目的是打開優酷主客的播放頁。

在這裡我們做了分類:

  • 當用戶未安裝優酷主客時,顯示一個中轉頁,提供下載按鈕供用戶跳轉到華為應用市場去下載優酷主客,當用戶安裝完優酷主客回來時,下載按鈕變成選集列表,對單集視頻則變成播放按鈕;
  • 當用戶已安裝優酷主客時,中轉頁自動打開優酷主客的播放頁,並退出。

數據請求

在優酷主客中,網路數據的請求都是通過統一的網路庫訪問的。由於優酷鴻蒙卡片並未集成網路庫,優酷鴻蒙卡片必須使用其他方式請求網路介面。

要實現在鴻蒙上發起數據請求,有兩個方案:

  • 一是針對每個數據請求介面,封裝一個新的HTTP Open API介面,客戶端可以通過HTTP(S)直接訪問;
  • 二是客戶端通過H5頁面里的JS版Network庫發起數據請求。

考慮到將來在鴻蒙系統上有可能實現更多其他的需求,且第一種方案有安全性的風險,所以最終我們採用了第二種方案。

前端業務使用的JS版本的網路庫,其使用方式是通過JS中的Promise機制來實現非同步回調,但是這種方式在Java中並不好實現對應的調用結構。所以這裡需要有一層封裝,將網路請求結果通過簡單回調來通知請求方。相應的在Java側需要對WebView註冊全局的JS對象,實現JS對象的回調方法,打通JS -> Java的調用通路。

這個方案在紙面上看著還不錯,但是在實際使用中會發現有嚴重的性能瓶頸。WebView本身是一個很重的控制項,在進程中首次創建的時候會比較耗時,有很多的so載入、初始化等工作。載入HTML是一個網路請求,耗時在百毫秒級,而載入並解析完HTML以後,還要再載入JS版本的網路庫,又是一次網路訪問。等JS網路庫載入並解釋執行後,才可以正常服務調用方。

要在這個過程中進行優化,這裡有主動權的地方就是載入HTML和JS網路庫這兩個文件。在鴻蒙系統中,WebView可以通過設置WebAgent來實現特定URL的劫持,將其轉化為讀取本地資源中的HTML和JS文件。

public class LoadAgent extends WebAgent {
    // ...

    @Override
    public ResourceResponse processResourceRequest(WebView webView, ResourceRequest request) {
        // mInterceptor用於識別HTML和JS網路庫的URL,並返回本地資源中的HTML和JS。
        ResourceResponse response = mInterceptor.intercept(request);
        if (response != null) {
            return response;
        }
        return super.processResourceRequest(webView, request);
    }
}

這可以將兩個百毫秒級的串列操作縮減為毫秒級,大大減少JS版本的網路庫的初始化時間。

數據緩存

從網路請求返回的卡片數據,除了用於即時渲染卡片之外,還會被保存一份到本地存儲中。如果下一次發起網路請求的時候,無法正常訪問網路(例如手機重啟後一時連不上網路),則可以使用緩存中的卡片數據先渲染一下,使用戶不至於完全看不到內容。這就需要有一套卡片數據的緩存管理能力。

針對卡片數據的特點,我們使用了兩個資料庫表來存儲卡片的緩存數據。根據卡片大小不同,請求中會提供不同的參數給服務端。反過來說,同樣大小的卡片發出請求的參數是相同的,也就是說同樣大小的卡片請求得到的數據是相同的。所以有一個表用來存儲不同大小的卡片數據,每個卡片大小對應一條記錄,包括唯一標識、卡片大小、請求返回的數據、時間戳等。

系統不限制用戶向桌面添加卡片的數量,同時在服務中心也可以有已經添加到桌面的卡片。所以同樣的卡片數據是可以被顯示在多個卡片上的。資料庫需要有一個表來記錄每一個卡片的信息,包括卡片的唯一標識、卡片大小、數據表中對應的記錄等。

優酷鴻蒙開發實踐 | 鴻蒙卡片開發

如果在Android中實現過ContentProvider,一般都會比較熟悉SQLite資料庫。通常ContentProvider需要管理大量、不同類型且互相有關聯的數據,這種需求用SQLite來實現最合適了。這裡管理卡片數據的緩存也具有同樣的特徵,並且鴻蒙系統也提供了SQLite資料庫的使用介面。典型的資料庫初始化操作如下:

// StoreConfig最常見的作用是配置資料庫名字。也可以配置存儲模式、加密等高級需求。
StoreConfig config = StoreConfig.newDefaultConfig(DB_NAME_FORM_STORE);

// RdbOpenCallback用於定義創建資料庫、升級資料庫結構版本等時機的回調。
RdbOpenCallback callback = new FormStoreOpenCallback(context);

DatabaseHelper helper = new DatabaseHelper(context);

// RdbStore是資料庫的封裝類,最終的增刪改查操作都通過它來進行。
RdbStore store = helper.getRdbStore(config, CURRENT_VERSION, callback);

具體的增刪改查操作就不一一列舉了。

數據更新

前面聲明卡片一節提到了config.json中,updateEnabled、updateDuration定義了卡片的數據更新機制。

其中updateEnabled用於指定是否通過系統來自動更新卡片數據,如果希望由應用自身觸發數據更新,這個可以設置為false。優酷卡片的場景是希望系統能夠自動更新卡片數據的,所以設為了true。

在updateEnabled設為true的情況下,updateDuration才有意義。updateDuration用於指定更新的時間間隔。鴻蒙系統還支持固定時間更新,通過指定scheduledUpateTime來設置更新時間。updateDuration和scheduledUpateTime只能選擇其中一種方式。

優酷卡片選擇了用updateDuration指定更新間隔。為了避免將來使用卡片的用戶多了,對服務端產生過大的壓力,更新間隔被控制在4小時,這樣用戶在上午、下午、晚上等不同時段去看卡片時,內容都會有更新。

但是有些情況下,優酷卡片自身的邏輯也會更新卡片數據,所以為了避免兩種更新策略衝突而導致更密集的更新,或者長時間不更新,updateDuration被指定為1,即每半小時系統就會調用一次onUpdateForm()。在onUpdateForm()中,會判斷上一次實際發生更新的時間,使更新間隔保持在4小時左右。

容錯處理

考慮到一些極端情況,例如用戶安裝優酷後,在沒有網路的情況下就添加了桌面卡片。此時卡片的數據請求是沒有返回的,同時由於剛安裝,也沒有緩存數據,所以卡片展示不出任何數據,只有灰色的打底圖作為背景。此時如果點擊卡片,也沒有任何視頻信息,也就無法跳轉到某個特定視頻的播放頁,只能顯示一個載入失敗的提示,等用戶網路恢復後,通過刷新得到有效數據。

空白卡片點擊卡片後的空白頁面
優酷鴻蒙開發實踐 | 鴻蒙卡片開發優酷鴻蒙開發實踐 | 鴻蒙卡片開發

展望

現在優酷鴻蒙版的桌面卡片已經隨著鴻蒙系統的發布,正式上線了。在鴻蒙系統的手機上,從華為應用市場安裝的優酷主客就已經附帶了優酷卡片的能力。

由於這是一個全新的開發技術棧,早期發布的應用肯定會有一些改進空間。從現在看來主要有以下一些方面:

  1. 性能
    由於數據請求和埋點用到了JS庫,並且在WebView中運行,這使得運行時效率比Java要低,還要處理WebView與外界的交互,對性能有較大影響。雖然已經有了一些措施來減少這方面的影響,但是後續還是需要繼續挖掘潛力
  2. 監控
    後續還需要補足JS側崩潰等信息收集的能力。
  3. 線上配置能力
    優酷主客可以通過各種遠程配置平台下發各種配置信息。而鴻蒙上由於體積限制無法自帶相關的庫。今後需要考慮使用其他方式實現遠程配置能力。

最後,10月20日將上線《優酷鴻蒙開發實踐》系列技術文章第二篇,為大家介紹如何實現Android/鴻矇混合打包的流程。感謝關注,我們下篇技術實踐再見。

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/222783.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-09 14:12
下一篇 2024-12-09 14:12

相關推薦

發表回復

登錄後才能評論