一个音乐播放器的设计流程,含完整实例

一个音乐播放器的设计流程,含完整实例

2023年7月11日发(作者:)

⼀个⾳乐播放器的设计流程,含完整实例前⾔上篇⽂章给⼤家分享了我的WanAndroid项⽬,简要描述了⼀下项⽬的整体结构。由于作者⾮常喜欢听⾳乐,鉴于对⾳乐的情怀,就在⼀个技术社区App中强⾏加了⼀个⾳乐播放功能。关于这个播放器的设计,我也花了很多⼼思,应⽤了很多设计模式,今天就逐个给⼤家进⾏分型。先细⼼阅读,⽂末会给出github链接1. 需求背景设计⼀个⾳乐播放功能,包含⼀个播放界⾯,包含播放/暂停、上⼀⾸、下⼀⾸、播放列表、播放模式、进度更新等,返回到⾸页⼜⼀个悬浮窗包含播放状态、⾳乐信息,⼤概就长下⾯这样:image当前这是第⼀版,功能⽐较简单,后⾯我会持续完善。2. 定义播放器第⼀步肯定要先来定义⼀个⾳乐播放器,这⾥采⽤的是Android原⽣提供的MediaPlayer,使⽤MediaPlayer之前先考虑⼀个事情,可以直接将MediaPlayer定义在业务中吗?答案是否定的,为什么?⼤概有如下⼏点:如果将MediaPlayer与业务进⾏耦合,每次做修改势必会影响到业务,进⽽会产⽣不可预期的错误。如果某⼀天需要将MediaPlayer替换,定会牵扯到⼤量代码,那代价会相当之⼤.如何解决上述问题?其实我们可以先设计⼀个接⼝,该接⼝包含⼀个⾳乐播放器所有功能。如下interface IPlayer { ... ... /** * 播放新的⾳频 * @param path 本地路径 */ fun play(path: String) /** * 播放 */ fun resume() /** * 暂停 */ fun pause() /** * 停⽌播放,释放播放内容 */ fun stop() ... ...}本篇⽂章会侧重讲设计思想,所以为了节省篇幅我只会贴出部分关键代码,下同。定义⼀个类MediaPlayerHelper实现IPlayer接⼝,内部通过MediaPlayer实现每个⽅法对应的功能,并基于IPlayer接⼝编程。这种写法其实就是设计原则中的基于接⼝⽽⾮实现编程,好处就是隐藏了具体实现,修改具体实现不会影响到上层业务。并且符合开闭原则(对 扩展开放、修改关闭),如果需要对MediaPlayer替换,直接写⼀个类实现IPlayer接⼝中的功能,然后对⽬标类做替换即可。⽤⼀段代码表⽰: private val playerHelper: IPlayer = MediaPlayerHelper() //替换为 private val playerHelper: IPlayer = XXPlayerHelper()关于这部分完整代码可⾄package _(包名)⽬录下参考3. 状态管理⼀个⾳乐播放器通常要与整个App的⽣命周期保持⼀致,并且多处UI状态如:Notification、播放页、⾸页悬浮必须保持⼀致,所以此时我们需要⼀个单例来维护播放状态与⾳乐信息,这个单例⼤概长这样:class PlayerManager private constructor(){ //单例创建 companion object { val instance: PlayerManager by lazy(mode = ONIZED) { PlayerManager() } } .... private val playerHelper: IPlayer = MediaPlayerHelper()

fun pause() { ... () }

private fun resume() { ... () } ... ...}PlayerManager由单例模式实现,内部包含了所有IPlayer⽅法,通过PlayerManager指使IPlayer去实现具体的播放操作,并且⽣命周期与App进程⼀致,可在App任意地⽅操作播放器。PlayerManager作⽤⼤概有如下⼏点:将⽬标对象IPlayer与具体业务进⾏隔离内部维护了观察者(后⾯会讲到),对状态同⼀做分发。统⼀管理播放模式和播放列表结合其特性分析,PlayerManager实则也是⼀个代理类。如果想对⾳频播放做⼀些附加操作,⽐如记录播放⽇志、播放时长等,都可以统⼀在PlayerManager中实现。为了进⼀步符合单⼀设计原则像播放列表之类的我都抽成了单独的类去管理。完整代码可⾄package _(包名)下参考4. 状态分发基于上⾯的设计,我们如何在具体的界⾯做UI渲染与交互呢?⾸先来尝试第⼀种⽅案:进⼊到具体的Activity/Fragment,通过PlayerManager获取到播放信息与状态,填充到具体的View当播放状态、信息改变时如点击了暂停按钮,通过PlayerManager暂停⾳频,然后⼿动将对应的View置为暂停状态关于上述⽅案,如果只有播放/暂停⼀个按钮没啥问题,⼤胆去使⽤吧。但实际上我们⾯临的时多界⾯中的多操作,加⼀块有⼀⼆⼗个,⽽且不同界⾯的状态信息也必须保持⼀致,还按照这种⽅式去写,相信我,你会欲死欲仙的。关于这种⽅案,我的答案是:弃之第⼆种⽅案第⼀种⽅案⾯临的问题是:状态与UI容易产⽣⼀致性问题。那么我们能不能做⼀种设计,播放让状态去驱动UI?何为状态驱动UI:顾名思义,就是播放状态改变后第⼀时间通知到视图层,视图层只做UI渲染。在此背景下我⼜想到了另⼀种设计模式观察者模式,只需将视图层定义为观察者,随后与被观察者PlayerManager进⾏绑定,统⼀由PlayerManager下发播放信息,视图层拿到状态第⼀时间对UI进⾏渲染。捋清了思路我们来做代码上的设计⾸先定义⼀个观察者接⼝:interface AudioObserver { /** * 歌曲信息 * 空实现,部分界⾯可不⽤实现 */ fun onAudioBean(audioBean: AudioBean){} /** * 播放状态,⽬前有四种。可根据类型进⾏扩展 * release * start * resume * pause * * 空实现,部分界⾯可不⽤实现 */ fun onPlayStatus(playStatus:Int){} /** * 当前播放进度 * 空实现,部分界⾯可不⽤实现 */ fun onProgress(currentDuration: Int,totalDuration:Int){} /** * 播放模式 */ fun onPlayMode(playMode:Int)}实现了该接⼝就可被视为观察者被观察者是PlayerManager,当内部状态发⽣改变时统⼀通知到观察者对象。关于管理观察者的代码⼤概是这样的:class PlayerManager private constructor(){ /** * ⾳乐观察者集合,⽬前有三个 * 1.播放界⾯ * 2.悬浮窗 * 3.通知栏 */ private val observers = mutableListOf()

private fun resume() { ... () //状态改变,通知观察者 sendPlayStatusToObserver() } private fun pause() { ... () //状态改变,通知观察者 sendPlayStatusToObserver() }

/** * 给观察者发送播放状态 */ private fun sendPlayStatusToObserver() { h { Status(playStatus) } }

/** * 注册观察者 */ fun register(audioObserver: AudioObserver) { (audioObserver) //TODO 注册时⼿动更新观察者,相当于粘性通知 notifyObserver(audioObserver) } /** * 解除观察者 */ fun unregister(audioObserver: AudioObserver) { (audioObserver) } /** * ⼿动更新观察者 */ private fun notifyObserver(audioObserver: AudioObserver) { ... ... }}有了上⾯的⾻架代码,将Notification、播放页、⾸页悬浮实现AudioObserver接⼝并且与PlayerManager进⾏绑定,在对应的⽅法中做视图渲染,这样就可以实现状态驱动UI,解决了状态与UI的⼀致性问题。同时可以基于此模式在任意处做播放信息的视图展⽰,对扩展开放,修改关闭,⽆处不在的开闭原则。数据绑定关于这个项⽬我使⽤到了Jetpack中的DataBinding,将状态数据与View进⾏绑定,当⼏个观察者观察到PlayerManager状态改变时,由DataBinding⾃动渲染到View中。到此我们就实现了⼀个真正的状态驱动UI,从状态分发到UI渲染之间没有任何多余操作,⼀⽓呵成~~关于PlayerManager和AudioObserver代码在package _(包名)⽬录下综上所述通过基于IPlayer接⼝编程,将功能组件与业务做隔离通过单例实现PlayerManager使播放信息在进程中共享PlayerManager实则也是⼀个代理类,将播放逻辑与具体业务进⾏隔离,并且在内部统⼀管理了观察者和播放列表基于观察者模式,解决了状态与UI的⼀致性问题最后由DataBinding将状态数据与View进⾏绑定,真正的实现状态驱动UI关于播发模块的代码在项⽬中的路径,⽂章中已经给出,请仔细阅读。

发布者:admin,转转请注明出处:http://www.yc00.com/web/1689067091a202407.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信