我把分布式音乐播放器适配了Stage模型
然而从API 9开始,Ability框架引入了Stage模型作为第二种应用框架形态,Stage模型将Ability分为PageAbility和ExtensionAbility两大类,其中ExtensionAbility又被扩展为ServiceExtensionAbility、FormExtensionAbility、DataShareExtensionAbility等一系列ExtensionAbility,以便满足更多的使用场景。新模型接口中有AbilityStage/WindowStage的概念,这个Stage本身有舞台的意思,寓意是给开发者一个新的展现舞台。Stage模型的设计,主要是为了开发者更加方便地开发出分布式环境下的复杂应用。下表给出了两种模型在设计上的差异:

可以看得出来,新的模型设计的主要目标是把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开发套件上,效果如下图所示:

可以看到,此次更新,不仅使用了Stage模型适配,还使用ArkTS增加了一个音乐播放器首页列表的界面,以及播放时使用属性动画,实现了一个播放音乐时“唱片旋转”的动画效果。这次使用Stage模型适配样例,主要是修改了如下几个地方:
修改点1:代码目录的调整

可以看到,相对于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替换。
相关文档请参考
新增首页页面,和播放列表页的动画,不是本文的重点,大家可以参考代码自行学习。
总结
OpenHarmony的FA模型能力已经停止演进,后续将会增强Stage模型。此次将现有的样例代码适配Stage模型,虽然整体代码修改量不大,但因为惯性思维以及API的变化,期间还是踩了不少坑。我已在OpenHarmony知识体系仓中更新了样例代码,欢迎开发者来参考和指正问题,建议新上手OpenHarmony的开发者可以直接学习使用新的Stage模型来开发应用。前面提到在Stage模型里,ExtensionAbility又被扩展为ServiceExtensionAbility、FormExtensionAbility、DataShareExtensionAbility等一系列ExtensionAbility,这个样例目前还没有涉及到,待后续进一步学习,通过ExtensionAbility把音乐播放实现成一个后台服务,从而实现应用在后台时也能继续播放音乐,届时将持续更新这个应用,也欢迎大家一起共建。
分布式音乐播放器样例地址
https://growing.openharmony.cn/mainPlay/detail?sampleId=3742

我把分布式音乐播放器适配了Stage模型的更多相关文章
- iOS开发拓展篇—音频处理(音乐播放器2)
iOS开发拓展篇—音频处理(音乐播放器2) 说明:该文主要介绍音乐播放界面的搭建. 一.跳转 1.跳转到音乐播放界面的方法选择 (1)使用模态跳转(又分为手动的和自动的) (2)使用xib并设置跳转 ...
- swift3.0 简单直播和简单网络音乐播放器
本项目采用swift3.0所写,适配iOS9.0+,所有界面均采用代码布局. 第一个tab写的是简单直播,传统MVC模式,第二个tab写的是简单网络音乐播放器.传说MVVM模式(至于血统是否纯正我就不 ...
- 如何用vue打造一个移动端音乐播放器
写在前面 没错,这就是慕课网上的那个vue音乐播放器,后台是某音乐播放器的线上接口扒取,虽然这类项目写的人很多,但不得不说这还是个少有的适合vue提升的好项目,做这个项目除了想写一个比较大并且功能复杂 ...
- SE Springer小组之《Spring音乐播放器》可行性研究报告三、四
3 对现有系统的分析 由于本次可行性分析主要是建立在团队自行实现一个音乐软件的目标上,并不是在一个现有系统的基础上开发改进的新系统.因此这里将分析一款市面上已经存在的音乐软件(以下称为W音乐),并为之 ...
- 卡拉OK效果的实现-iOS音乐播放器
自己编写的音乐播放器偶然用到这个模块,发现没有思路,而且上网搜了搜,关于这方面的文章不是很多,没找到满意的结果,然后自己也是想了想,最终实现了这种效果,想通了发现其实很简单. 直接上原理: 第一种: ...
- 小菜学习Winform(二)WMPLib实现音乐播放器
前言 现在网上有很多的音乐播放器,但好像都不是.net平台做的,在.net中实现音乐文件的播放功能很简单,下面就简单实现下. SoundPlayer类 在.net提供了音乐文件的类:SoundPlay ...
- 【大结局】《从案例中学习JavaScript》之酷炫音乐播放器(四)
这是之前写的用H5制作的音乐播放器,前三节其实已经做得差不多了,音轨的制作原理已经在上一节说明,不过一直还没有和音乐对接. 本章作为该系列的一个完结篇,我会专门把动态音轨的实现代码贴出来,demo地址 ...
- Andriod小项目——在线音乐播放器
转载自: http://blog.csdn.net/sunkes/article/details/51189189 Andriod小项目——在线音乐播放器 Android在线音乐播放器 从大一开始就已 ...
- Android开发6:Service的使用(简单音乐播放器的实现)
前言 啦啦啦~各位好久不见啦~博主最近比较忙,而且最近一次实验也是刚刚结束~ 好了不废话了,直接进入我们这次的内容~ 在这篇博文里我们将学习Service(服务)的相关知识,学会使用 Service ...
- android 音乐播放器
本章以音乐播放器为载体,介绍android开发中,通知模式Notification应用.主要涉及知识点Notification,seekbar,service. 1.功能需求 完善音乐播放器 有播放列 ...
随机推荐
- 双层循环练习,pass_break_continue,和for循环---day06
1.双层循环练习 2.pass_break_continue pass:在代码块中无代码可写时,可用pass占位 break:终止当前循环,只能应用在循环里 continue:跳过当前循环,从下一次开 ...
- J-link虚拟串口波特率异常问题
J-LINK V9以上自带了虚拟串口,使用非常方便. 但最近遇到问题,发现打开虚拟串口时电脑接收到的是乱码.到官网搜索了一下,发现最高波特率是115200,我使用的是256000,于是降低波特率. 官 ...
- 【ACM专项练习#02】输入整行字符串、输入值到vector、取输入整数的每一位
输入整行字符串 平均绩点 题目描述 每门课的成绩分为A.B.C.D.F五个等级,为了计算平均绩点,规定A.B.C.D.F分别代表4分.3分.2分.1分.0分. 输入 有多组测试样例.每组输入数据占一行 ...
- 进击的 AI 生成,创造性的新世界!
2022年,AI艺术生成文本生成图像的AI绘画生成器如雨后春笋般涌现,以一幅幅"不明觉厉"的AI作品进入大众视野.从2月Disco Diffusion爆火,仅两个月后OpenAI发 ...
- 【Azure 应用服务】App Server 部署后,Docker报错,找不到8080端口
问题描述 App Service for Container. Docker Image 推送到ACR(向 Azure 容器注册表), 配置App Service并部署成功了.查看Docker日志( ...
- 线段树-多个懒标记pushdown
P3373 [模板]线段树 2 这里需要用到两个懒标记,一个懒标记为add,记录加,另一个懒标记为mul,记录乘. 我们需要规定一个优先级,然后考虑如何将懒标记下传. 这里无非有两种顺序,一种是先乘后 ...
- foundation部分学习记录(更正更新中……)
foundation部分学习记录(更新中--) 从FDB的角度看,它对上层只提供有序+事务+KV存储的抽象. 设计原则 模块化分割,尽量细分且模块之间相互解耦 例如事务系统内,其提交(write pa ...
- Obsidian 设置快捷键 Ctrl+Shift+J 打开OB(未启动则启动,启动未激活则激活,已激活则最小化)- autoHotKey
Obsidian 设置快捷键 Ctrl+Shift+J 打开OB(未启动则启动,启动未激活则激活,已激活则最小化)- autoHotKey 需求 将Obsidian作为主笔记软件使用,设置个快捷键,配 ...
- openlayers.org 百度地图 静态化 同类产品
openlayers 官网 https://openlayers.org/ 发现不好打开,修改dns 114.114.114.114 202.99.104.68 打开快了许多 https://blog ...
- C#获取Description特性的扩展类
C#中Description特性主要用于枚举和属性,方法比较简单,记录一下以便后期使用. 扩展类DescriptionExtension代码如下: using System; using System ...