HarmonyOS 应用开发《掌上英语》第31篇:AudioPlayer 单例封装:HarmonyOS 音频播放最佳实践 📅 2026/7/23 1:38:22 31AudioPlayer 单例封装HarmonyOS 音频播放最佳实践一、引言在 HarmonyOS 英语学习 App 中音频播放是核心功能之一——单词发音、例句朗读、听力练习都需要稳定可靠的音频播放能力。如果每个页面各自创建和销毁播放器实例不仅会造成资源浪费还会出现多个音频同时播放的混乱局面。为此我们设计了一个基于单例模式的AudioPlayer类集中管理AVPlayer的完整生命周期。本文将基于实际源码深入分析单例模式在多媒体播放场景下的应用、AVPlayer 的异步初始化、状态管理以及异常处理机制。二、单例模式的设计与实现2.1 为什么需要单例音频播放器属于典型的资源型对象它持有底层的媒体解码器、音频输出设备等系统资源。如果每个页面都new AudioPlayer()会导致资源竞争多个播放实例同时操作音频设备产生杂音或冲突内存泄漏页面退出后忘记释放播放器造成资源泄漏状态混乱无法统一获知当前是否有音频正在播放单例模式确保全局只有一个AudioPlayer实例所有页面共享同一个播放器从根本上解决了上述问题。2.2 代码实现import{media}fromkit.MediaKit;import{BusinessError}fromkit.BasicServicesKit;import{Logger}from./Logger;exportclassAudioPlayer{privatestaticinstance?:AudioPlayer;privateavPlayer:media.AVPlayer|nullnull;privatecurrentUrl:string;privateconstructor(){}privateasyncinitPlayer():Promisevoid{try{this.avPlayerawaitmedia.createAVPlayer();this.initPlayerEvents();}catch(e){Logger.error(AudioPlayer,初始化失败);}}publicstaticgetInstance():AudioPlayer{if(!AudioPlayer.instance){AudioPlayer.instancenewAudioPlayer();AudioPlayer.instance.initPlayer();}returnAudioPlayer.instance;}// ...}2.3 设计要点解析私有构造函数private constructor()防止外部通过new AudioPlayer()创建实例强制所有调用方走getInstance()工厂方法。延迟初始化AudioPlayer.instance初始为undefined只有在第一次调用getInstance()时才创建实例并初始化 AVPlayer。这种Lazy Initialization模式避免了 App 启动时不必要的资源开销。异步初始化media.createAVPlayer()是一个异步操作initPlayer()方法被设计为async在创建成功后挂载事件监听器。这里有一个细节如果 initPlayer 尚未完成时第二个线程调用了getInstance()instance已经不为空但avPlayer可能还是null。在实际使用中play()方法内部会判空处理确保安全。三、AVPlayer 的异步创建流程privateasyncinitPlayer():Promisevoid{try{this.avPlayerawaitmedia.createAVPlayer();this.initPlayerEvents();}catch(e){Logger.error(AudioPlayer,初始化失败);}}media.createAVPlayer()是 HarmonyOS 提供的媒体框架 API它返回一个Promisemedia.AVPlayer。在等待 Promise resolve 的过程中初始化函数不会阻塞主线程。创建成功后紧接着调用initPlayerEvents()注册事件回调。异常处理使用try/catch包裹整个初始化过程。如果创建失败例如系统媒体服务不可用日志系统会记录错误avPlayer保持为null后续所有播放操作都会因空指针检查而安全跳过。四、事件系统的初始化privateinitPlayerEvents():void{if(!this.avPlayer)return;this.avPlayer.on(error,(err:BusinessError)Logger.error(AudioPlayer,错误:${JSON.stringify(err)}));this.avPlayer.on(endOfStream,()Logger.info(AudioPlayer,播放完成));}事件监听是音频播放可靠性的基石。这里注册了两个最关键的事件error 事件当播放过程中发生解码错误、网络超时或文件格式不支持等问题时触发。使用JSON.stringify(err)将错误信息序列化输出到日志便于排查问题。endOfStream 事件音频文件播放完毕后触发用于通知播放完成。在更复杂的场景中可以在这里触发播放下一个音频或更新 UI 状态。值得注意的是实际生产环境还可以注册stateChange事件来监听 AVPlayer 的状态流转这部分将在后续文章中深入分析。五、核心方法详解5.1 play 方法——智能播放控制publicplay(url:string):void{if(!url||url){Logger.warn(AudioPlayer,URL为空);return;}if(!this.avPlayer)return;if(this.currentUrlurlthis.avPlayer.stateplaying){this.stop();return;}this.currentUrlurl;this.avPlayer.stop();letsrcurl.startsWith(http)?url:rawfile/${url};this.avPlayer.urlsrc;this.avPlayer.prepare().then((){if(this.avPlayer)this.avPlayer.play();});}play()方法体现了几个精心设计的逻辑防御性编程首先检查 URL 是否为空防止空值导致崩溃重复播放检测如果当前正在播放同一个 URL则调用stop()停止——这符合用户点击同一个发音按钮再次播放的交互预期资源重置每次播放前先stop()确保播放器回到可配置状态远程/本地资源适配通过startsWith(http)判断是远程 URL 还是本地 rawfile 资源自动拼接路径异步准备prepare().then()在准备完成后才调用play()符合 AVPlayer 的状态机要求5.2 stop / pause / release 方法publicstop():void{if(!this.avPlayer)return;if(this.avPlayer.stateplaying||this.avPlayer.statepaused)this.avPlayer.stop();}publicpause():void{if(!this.avPlayer)return;if(this.avPlayer.stateplaying)this.avPlayer.pause();}publicrelease():void{this.stop();if(this.avPlayer)this.avPlayer.release();this.avPlayernull;AudioPlayer.instanceundefined;}每个方法都遵循相同的模式空值检查 → 状态判断 → 执行操作。stop()和pause()的区别在于stop()停止播放释放解码资源播放器回到prepared或initial状态。再次播放需要重新设置 URL 并prepare()pause()暂停播放保留解码状态。调用play()可直接恢复release()方法用于彻底销毁播放器在所有操作后将instance置为undefined使单例回归初始状态这在应用退出或需要重置音频系统时非常关键。六、状态管理与安全边界AudioPlayer 内部维护了currentUrl状态变量用于追踪当前加载的音频资源。结合 AVPlayer 自身的状态机形成了双层状态管理场景currentUrlavPlayer.state行为首次播放设为新URLidle → prepared → playing正常播放流程重复点击与传入URL相同playing停止播放切换音频更新为新URL先stop再prepare→play无缝切换URL为空不变不变直接返回七、异常处理最佳实践在整个 AudioPlayer 中异常处理遵循分层策略第一层参数校验——play()开头检查 URL 是否为空用Logger.warn记录警告而非 Error因为空 URL 属于预期内的调用方失误。第二层状态校验——每个操作前检查this.avPlayer是否为null防止在初始化失败或已释放后继续操作。第三层异步异常——media.createAVPlayer()的 try/catch 捕获创建失败prepare().then()中理论上还应链式.catch()处理准备失败的情况当前源码中未包含生产环境建议补全。八、总结AudioPlayer 的单例封装设计在代码层面实现了三个核心目标资源复用全局共享一个 AVPlayer 实例避免重复创建和销毁接口简洁对外暴露play/stop/pause/release四个方法调用方无需关心底层状态机安全可靠防御性编程 分层异常处理确保任何异常场景都不会导致应用崩溃这套设计模式不仅适用于音频播放对于相机预览、视频播放、录音等所有需要独占系统资源的场景都具有普遍的参考价值。