我把分散式音樂播放器適配了Stage模型

OpenHarmony開發者社群發表於2022-11-07

        OpenAtom OpenHarmony(以下簡稱“OpenHarmony”)應用開發自API 8及其更早版本一直使用的是FA模型進行開發。FA模型是Feature Ability的縮寫,它和PA(Particle Ability)兩種型別是過往長期推廣的術語,深入人心。

        然而從API 9開始,Ability框架引入了Stage模型作為第二種應用框架形態,Stage模型將Ability分為PageAbility和ExtensionAbility兩大類,其中ExtensionAbility又被擴充套件為ServiceExtensionAbility、FormExtensionAbility、DataShareExtensionAbility等一系列ExtensionAbility,以便滿足更多的使用場景。新模型介面中有AbilityStage/WindowStage的概念,這個Stage本身有舞臺的意思,寓意是給開發者一個新的展現舞臺。Stage模型的設計,主要是為了開發者更加方便地開發出分散式環境下的複雜應用。下表給出了兩種模型在設計上的差異:

undefined

        可以看得出來,新的模型設計的主要目標是把UI與Ability分離,即從架構設計層面,規範開發者編寫業務邏輯和UI互動的開發方式。透過資料把UI和業務邏輯解耦,開發者在Ability中產生資料,資料傳遞給UI框架後,利用ArkTS宣告式框架的特點,UI=F(state),透過資料驅動UI變化。這樣的設計是為了更好地支援Ability實現跨端遷移和多端協同,即資料都是儲存在Ability裡,繼而透過資料驅動UI展示。此外,FA模型每個Ability使用一個VM例項,而Stage模型整個程式只使用一個VM例項,減少程式記憶體佔用,應用內狀態在程式內共享。

        分散式音樂播放器,是今年上半年我基於OpenHarmony 3.1,參考OpenHarmony JS分散式音樂播放的Sample程式碼,使用ArkTS新寫的樣例,當時的主要目的就是為了學習ArkTS開發頁面。此次適配Stage模型後,在潤和大禹系列HH-SCDAYU200開發套件上,效果如下圖所示:

undefined

        可以看到,此次更新,不僅使用了Stage模型適配,還使用ArkTS增加了一個音樂播放器首頁列表的介面,以及播放時使用屬性動畫,實現了一個播放音樂時“唱片旋轉”的動畫效果。這次使用Stage模型適配樣例,主要是修改瞭如下幾個地方:

修改點1:程式碼目錄的調整

undefined

        可以看到,相對於FA的目錄結構,首先是在最上層目錄裡,增加了一個AppScope目錄,這個目錄下也是resources下的資原始檔,比如string.json,圖片等內容。這個目錄裡的資原始檔,會在編譯時拼接到具體的hap內編譯,因此可以把不同hap包裡的公用資源提取到這個目錄下。

        此外是增加了AbilityStage.ts這個檔案,它是Hap及載入入口,開發者可以基於它派生完成hap的初始化以及指定多個例項開發。AbilityStage可以配合ApplicationContext監聽/管理程式內元件的生命週期,感覺是有點充當了FA模型裡的app.ets的作用。

        其它的檔案也有小的變化,如配置檔案,pages位置等都有調整。所以建議還是新建一個stage模型的工程,然後把之前的程式碼逐步複製過來,然後修改問題。

修改點2:獲取裝置列表,分散式拉起等API變化

        由於兩種模型的應用上下文不同,導致一些跟上下文相關的API大都有些變化,在SDK及文件中有明確標明哪些API是stage模型專用的。比如耳熟能詳的startAbility分散式拉起應用,在FA模型中是透過以下程式碼實現:

import featureAbility from '@ohos.ability.featureAbility';
   featureAbility.startAbility({
      want: wantValue
    }).then((data) => {
      CommonLog.info('startAbilityContinuation finished, ' + JSON.stringify(data))
      //拉起後,自我關閉
      featureAbility.terminateSelf((error) => {
        CommonLog.info('startAbilityContinuation terminateSelf finished, error=' + JSON.stringify(error))
      })
    }).catch((error) => {
      CommonLog.info('startAbilityContinuation error ' + JSON.stringify(error))
    })

        而在stage模型裡,由於不再有featureAbility,因此無法import featureAbility,進而無法使用featureAbility.startAbility拉起應用,進而使用getContext獲取上下文後,呼叫startAbility拉起應用。

getContext(this).startAbility(want).then((data) => {
      CommonLog.info('startAbilityContinuation finished, ' + JSON.stringify(data))
      //自我關閉
      getContext(this).terminateSelf((error) => {
        CommonLog.info('startAbilityContinuation terminateSelf finished, error=' + JSON.stringify(error))
      })
    }).catch((error) => {
      CommonLog.info('startAbilityContinuation error ' + JSON.stringify(error))
    })

        除了startAbility外,樣例裡使用到的獲取包含bundleName,裝置發現deviceManager的相關API都需要按照上述方法進行修改。

修改點3:資料從元件分離,提取到Ability中

        在分散式拉起時,需要傳遞當前播放的音樂和音樂的播放進度。在兩種模型裡,這些引數都是被設定在wantValue的parameters裡,透過startAbility傳出去。

let params = {
      index: this.playerManager.getCurrentMusicIndex(),
      seekTo: this.playerManager.getCurrentTimeMs(),
      isPlaying: this.isPlaying
    }
    let wantValue = {
      bundleName: this.bundleName,
      abilityName: 'com.madixin.music.MainAbility',
      deviceId: remoteDevice.deviceId,
      parameters: params
    }

        但在接收引數時,FA模型裡,是在當前元件的程式碼裡,透過featureAbility.getWant來獲取引數,如下程式碼。

featureAbility.getWant((error, want) => {
      CommonLog.info('restoreFromWant featureAbility.getWant=' + JSON.stringify(want))
      let status = want.parameters
      if (status != null && status.index != null) {
        this.playerManager.playSpecifyMusic(status.seekTo, status.index)
        this.isPlaying = true
        this.playAnimation()
      }
    })

        而使用Stage模型後,雖然引數傳遞的方式是一致的,但是無法直接在元件UI中獲取引數,而需要先在MainAbility.ts獲取引數want。此時如果要傳遞給元件,有多種方式,這裡我是使用的如下方式,即在MainAbility.ts的onCreate和onNewWant裡,把want賦值到globalThis裡,然後在UI元件裡,透過globalThis獲取引數。

// MainAbility.ts
    onNewWant(want, launchParams) {
        globalThis.newWant = want
        hilog.info(0x0000, 'MyOpenHarmonyPlayer', '%{public}s', 'onNewWant launchParam:' + JSON.stringify(launchParams) ?? '');
    }
    onCreate(want, launchParam) {
        globalThis.newWant = want
        hilog.info(0x0000, 'MyOpenHarmonyPlayer', '%{public}s', 'want param:' + JSON.stringify(want) ?? '');
        hilog.info(0x0000, 'MyOpenHarmonyPlayer', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? '');
    }
    
    // index.ets
    let newWant = globalThis.newWant
    CommonLog.info("aboutToAppear newWant:" + JSON.stringify(newWant))
    if (newWant !== null && newWant.parameters.hasOwnProperty("seekTo")) {
      this.playerManager.playSpecifyMusic(newWant.parameters.seekTo, newWant.parameters.index)
    }

        另外,瞭解到還有一種方式傳遞資料是使用AppStorage來關聯,比如在MainAbility.ts裡使用AppStorage.SetOrCreate<string>傳入資料,在UI元件裡,使用@StorageLink標籤修飾變數來獲取資料。  

除以上三點修改外,還有兩點值得說明下

        首先是因OpenHarmony 3.2後分布式能力限制智慧系統應用使用,需要提升apl等級:找到所使用API版本對應toolchains>版本號>lib>UnsgnedReleasedProfileTemplate.json,更改  "apl": "normal"為  "apl": "system_core"。

        其次是API 9以後區分了public-SDK和Full SDK。DevEco Studio預設下載的是public-SDK,它不包含系統應用所需要的高許可權API。當我們import deviceManager from '@ohos.distributedHardware.deviceManager'時,會發現裡面只有一個空的介面,沒有任何方法。雖然這不影響功能,但程式碼中必須使用@ts-ignore忽略typescript的告警,而且沒有語法提示。此時,需要使用full-SDK替換。

相關文件請參考

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/full-sdk-switch-guide.md

        新增首頁頁面,和播放列表頁的動畫,不是本文的重點,大家可以參考程式碼自行學習。

總結

        OpenHarmony的FA模型能力已經停止演進,後續將會增強Stage模型。此次將現有的樣例程式碼適配Stage模型,雖然整體程式碼修改量不大,但因為慣性思維以及API的變化,期間還是踩了不少坑。我已在OpenHarmony知識體系倉中更新了樣例程式碼,歡迎開發者來參考和指正問題,建議新上手OpenHarmony的開發者可以直接學習使用新的Stage模型來開發應用。前面提到在Stage模型裡,ExtensionAbility又被擴充套件為ServiceExtensionAbility、FormExtensionAbility、DataShareExtensionAbility等一系列ExtensionAbility,這個樣例目前還沒有涉及到,待後續進一步學習,透過ExtensionAbility把音樂播放實現成一個後臺服務,從而實現應用在後臺時也能繼續播放音樂,屆時將持續更新這個應用,也歡迎大家一起共建。

分散式音樂播放器樣例地址

我把分散式音樂播放器適配了Stage模型


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011554/viewspace-2922132/,如需轉載,請註明出處,否則將追究法律責任。

相關文章