行业资讯

iOS/macOS音频会话管理:AVAudioSession核心配置与实战指南

发布时间:2026/8/8 17:21:23
iOS/macOS音频会话管理:AVAudioSession核心配置与实战指南 1. 项目概述音频会话管理的基石在iOS和macOS的音频开发里AVAudioSession是你绕不开的核心组件。它不像直接播放或录制音频的API那样直观更像是一个隐形的“音频交通警察”负责协调你App内外的所有音频行为。很多开发者第一次接触它往往是在处理一些棘手的音频问题比如播放音乐时来了电话App的音频被中断后无法恢复或者想实现后台播放却发现App一切到后台声音就戛然而止又或者是录音时扬声器还在外放导致刺耳的啸叫。这些问题归根结底都是对AVAudioSession的Category类别、Mode模式、Options选项以及各种Error错误理解不透彻导致的。我自己在开发音频相关的App时没少在这个“交通警察”身上栽跟头。记得有一次做一个语音聊天应用明明前台录音和播放都好好的一切到后台录音就停了还报一些看不懂的错误码。折腾了大半天才发现是Category没设置对Options里漏了关键的.allowBluetoothA2DP选项导致系统认为我的App不需要在后台使用音频输入。从那以后我就把AVAudioSession的这些参数当作音频开发的“内功”来修炼。今天我就结合自己踩过的坑和项目经验把这套看似枯燥但至关重要的参数体系掰开揉碎了讲清楚。无论你是刚接触音频开发的新手还是想系统梳理一下的老手这篇文章都能帮你建立起清晰、实用的认知框架让你在应对各种音频场景时真正做到心中有数手中有策。2. 核心概念深度解析Category、Mode、Options与Error要玩转AVAudioSession首先得理解它的四大核心配置项Category、Mode、Options和Error。它们不是孤立的而是一个层层递进、相互配合的体系。你可以把AVAudioSession想象成一个功能强大的音频路由器Category决定了你这个App音频路由的基本策略和优先级Mode是在这个基本策略下的精细化场景适配Options则是一些可以额外开启或关闭的“开关”用来微调行为。而Error则是这个系统在运行过程中给你的反馈信号读懂它们是解决问题的关键。2.1 Category定义音频行为的基本策略Category是AVAudioSession配置的基石它向系统声明了你的App使用音频的主要意图。系统会根据你设置的Category来决定如何管理你的App音频与其他App音频、系统声音如铃声、通知之间的关系。选错了Category后续所有的工作都可能事倍功半。iOS提供了多个预定义的Category每个都有其明确的语义.ambient环境音类别。这是最“谦让”的类别。使用此类别的音频会被系统铃声和其他App的音频如正在播放的音乐打断并且当设备锁屏或App退到后台时音频会自动停止。它不会打断其他音频适合游戏背景音、音效等非核心音频体验。它不支持音频输入录音。.soloAmbient独奏环境音。这是.ambient的“独占”版本。它同样会在锁屏或退到后台时停止也不支持录音。但关键区别在于它会打断其他App的音频比如后台的音乐App。如果你的App声音是唯一重要的且不需要后台播放可以用这个。.playback播放类别。顾名思义主要用于音频/视频播放。它的核心特点是1) 即使设备锁屏或App退到后台音频也能继续播放前提是你在Capabilities中开启了Audio, AirPlay, and Picture in Picture后台模式2) 它会打断其他非必需类的音频如.ambient3) 默认情况下它不会被静音开关或锁屏静音。但它不支持音频输入。.record录音类别。专为录音设计。它会打断其他所有音频包括播放中的音乐并允许在后台录音同样需要开启后台模式。它不支持音频输出播放。如果你只想录音而不播放就用这个。.playAndRecord播放和录音类别。这是最强大、也是最常用的类别适用于语音通话、VoIP、音频录制带监听、音乐制作等需要同时输入输出的场景。它默认行为是1) 支持后台播放和录音2) 打断其他音频3) 默认使用听筒Receiver作为输出而不是扬声器Speaker。这是和.playback一个很大的行为差异需要特别注意。.multiRoute多路由类别。允许音频同时输出到多个设备比如同时输出到蓝牙耳机和内置扬声器。这是一个非常特殊的类别通常用于专业音频或演示App普通应用很少用到。.audioProcessing音频处理类别。用于那些不进行播放或录制但需要进行离线音频处理的App。它不占用音频硬件但允许你使用一些音频单元。注意从iOS 10开始Apple引入了基于Category的“中断策略”。例如当有电话接入时系统会根据你的Category决定是“混合”还是“中断”你的音频。.playAndRecord通常用于通话所以电话来时会被“混合”即你的音频可能被压低音量而非完全停止而.playback则可能被完全中断。理解这一点对处理音频中断恢复很重要。2.2 Mode在Category基础上的场景微调如果说Category定义了“你要做什么”播放、录音等那么Mode就定义了“你在什么具体场景下做”。Mode是对Category行为的进一步优化以适应特定的使用场景。不是所有Category都支持所有Mode系统有明确的对应关系。一些关键的Mode包括.default默认模式。不指定任何特殊场景使用Category的默认行为。.voiceChat语音聊天模式。这是为VoIP或视频通话应用设计的。当与.playAndRecordCategory结合使用时它会自动启用一系列优化1)设置音频为语音处理质量优化人声频段2)自动配置为使用听筒和麦克风并管理好回声消除3) 当有蓝牙耳机可用时优先使用它。这是实现微信语音通话、FaceTime Audio等体验的关键。.videoChat视频聊天模式。与.voiceChat类似但针对视频通话优化。除了人声优化它可能还会对音频/视频同步做一些处理。.gameChat游戏聊天模式。专为游戏内的语音聊天设计。它允许游戏背景音使用.ambient或.playback的音频与语音聊天音频更好地共存。.moviePlayback电影播放模式。用于视频播放App可能会优化音频以匹配视频内容并管理好音频的延迟。.measurement测量模式。用于需要极低延迟音频I/O的App如音频分析工具。.spokenAudio语音音频模式。用于播客、有声书等纯语音内容播放可能会启用一些有助于语音清晰度的音频处理。设置Mode的代码示例import AVFoundation let audioSession AVAudioSession.sharedInstance() do { try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: []) try audioSession.setActive(true) } catch { print(“设置音频会话失败: \(error)”) }2.3 Options精细控制行为的开关Options是一组可选的标志可以以数组的形式传递给setCategory(_:mode:options:)方法用来对选定的Category和Mode进行行为微调。多个Options可以用数组组合使用。常用的Options有.mixWithOthers允许与其他App音频混合。对于.playback和.playAndRecordCategory默认行为是打断其他后台音频。加上这个选项后你的音频就不会打断其他App的音频比如后台的音乐App而是与之混合播放。这对于一些背景音乐类App或需要尊重用户音乐选择的游戏很有用。.duckOthers压低其他音频。你的音频播放时系统会自动降低其他App音频的音量而不是完全中断当你的音频停止时其他音频音量恢复。这比直接中断体验更友好常用于导航App的语音提示、消息提示音等短暂音频场景。.allowBluetooth允许蓝牙音频设备。这个选项允许音频路由到蓝牙耳机HFP/HSP协议通常用于通话。对于需要语音通话的App使用.playAndRecord.voiceChat强烈建议加上此选项否则用户连接蓝牙耳机后可能无法正常使用麦克风。.allowBluetoothA2DP允许蓝牙A2DP设备。允许音频路由到支持A2DP协议的蓝牙设备如高品质音乐耳机、车载音响。注意A2DP设备通常只支持音频输出不支持麦克风输入。.defaultToSpeaker默认使用扬声器。对于.playAndRecordCategory默认输出是听筒。加上这个选项后当没有其他音频输出设备如有线耳机、蓝牙设备连接时会自动使用扬声器。这对于免提通话、录音回放等场景非常有用。.interruptSpokenAudioAndMixWithOthers这是一个比较特殊的选项在iOS 14.5引入。它允许你的音频打断系统的“朗读屏幕”等语音内容但同时又可以与其他媒体音频混合。Options组合使用示例 假设你在做一个播客App希望1) 后台播放2) 不打断用户可能正在播放的音乐3) 支持蓝牙耳机收听。try audioSession.setCategory(.playback, mode: .spokenAudio, options: [.mixWithOthers, .allowBluetoothA2DP])2.4 Error系统反馈与问题排查指南在配置或使用AVAudioSession时几乎所有的方法都可能抛出错误。捕获并理解这些错误是调试音频问题的第一步。错误通常是一个NSError对象其code属性对应着AVAudioSession.ErrorCode。一些常见且重要的错误码.cannotInterruptOthers(代码: 561015905)你的App当前音频会话配置Category不允许打断其他音频但你尝试激活会话(setActive(true))时系统无法满足。比如你的Category是.ambient它本身就不能打断别人如果你在已有音频播放时强行激活就可能报此错。解决方案要么改用.playback等有中断权限的Category要么在激活时使用.notifyOthersOnDeactivation选项后面会讲要么先停掉自己的会话。.insufficientPriority(代码: 561017449)你的App尝试激活音频会话但当前有更高优先级的音频会话正在活动比如来电、导航、其他使用.playAndRecord的App。你的激活请求被拒绝。解决方案监听音频中断通知AVAudioSession.interruptionNotification等待高优先级会话结束后再尝试激活。.resourceNotAvailable(代码: 561145203)请求的音频硬件资源不可用。例如尝试在模拟器上使用录音Category模拟器没有真实麦克风或者麦克风被其他App独占使用。.unspecified(代码: 2003329396)未指明的错误。需要查看userInfo中的更多信息或者结合控制台日志判断。.badParam(代码: -50)参数错误。比如传入了一个无效的Category或Mode枚举值。错误处理的最佳实践 永远不要忽略setCategory、setActive等调用可能抛出的错误。至少应该用do-catch块捕获并打印出来这是定位问题的黄金信息。do { try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .defaultToSpeaker]) try audioSession.setActive(true, options: .notifyOthersOnDeactivation) // 激活时通知其他App } catch let error as NSError { print(“音频会话配置失败 - 错误码: \(error.code), 描述: \(error.localizedDescription)”) // 这里可以根据不同的错误码给用户友好的提示或者进行降级处理。 if error.code AVAudioSession.ErrorCode.insufficientPriority.rawValue { // 提示用户当前有更高优先级的音频如来电请稍后再试 } }3. 实战配置策略与场景化应用理解了基本概念后我们来看看如何在实际项目中组合运用这些参数。不同的功能场景对应着不同的配置“配方”。3.1 场景一音乐/播客播放器核心需求后台连续播放、支持锁屏和控制中心控件、可与其他音频混合尊重用户选择、支持蓝牙和AirPlay。推荐配置let audioSession AVAudioSession.sharedInstance() do { // 使用 .playback 类别保证后台播放能力 // 使用 .spokenAudio 模式优化语音内容播客或用 .default // 使用 .mixWithOthers 允许与音乐App共存 // 使用 .allowBluetoothA2DP 和 .allowAirPlay 支持无线音频 try audioSession.setCategory(.playback, mode: .spokenAudio, // 或 .default options: [.mixWithOthers, .allowBluetoothA2DP, .allowAirPlay]) try audioSession.setActive(true) } catch { print(“配置失败: \(error)”) }注意事项后台模式别忘了在Xcode项目的Signing Capabilities中添加Background Modes并勾选Audio, AirPlay, and Picture in Picture。否则App退到后台几秒后音频就会被系统挂起。控制中心与锁屏显示播放信息和控制需要使用MPNowPlayingInfoCenter和远程控制事件Remote Command这与AVAudioSession是两套系统但需要配合使用。.mixWithOthers的副作用开启后你的App将不会自动打断其他音频。如果你的App是主音乐播放器这可能是期望行为但如果你希望播放时独占音频就不要加这个选项。3.2 场景二语音通话/VoIP应用如微信语音核心需求同时录音和播放全双工、优先使用听筒或蓝牙耳机、优化人声、回声消除、后台持续运行。推荐配置do { // 核心.playAndRecord 类别 .voiceChat 模式 // .allowBluetooth 是关键否则蓝牙耳机无法用于通话。 // .defaultToSpeaker 让用户在免提时可一键切换到扬声器。 try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .defaultToSpeaker]) try audioSession.setActive(true) } catch { print(“配置失败: \(error)”) }实操心得蓝牙支持.allowBluetooth选项对于通话类App是必须的。没有它用户即使连接了蓝牙耳机声音可能还是从手机听筒或扬声器出来麦克风也无法使用。输出路由.playAndRecord类别默认输出是听筒。这是为了贴近耳朵通话的自然体验。通过.defaultToSpeaker选项我们改变了无外接设备时的默认行为。你还需要监听AVAudioSession.routeChangeNotification来动态处理用户插拔耳机、连接蓝牙设备等路由变化并可能通过overrideOutputAudioPort(_:)方法手动切换扬声器/听筒。音频单元.voiceChat模式会自动启用系统级的回声消除AEC和自动增益控制AGC。对于大多数应用这已经足够。如果你有更高的音质或自定义处理需求可能需要深入音频单元Audio Unit编程。3.3 场景三录音机仅录音核心需求高保真录音、后台录音、独占麦克风。推荐配置do { // 使用 .record 类别专注于录音 // 模式通常用 .default 或 .measurement如需低延迟 // 不需要混合选项因为录音需要独占音频输入 try audioSession.setCategory(.record, mode: .default, options: []) try audioSession.setActive(true) } catch { print(“配置失败: \(error)”) }关键点.record类别会打断所有其他音频输出。如果你的App在录音时还需要播放引导音或效果音那就必须使用.playAndRecord类别。3.4 场景四游戏音效核心需求音效与背景音乐播放、可被系统声音如来电打断、通常不需要后台播放。推荐配置do { // 使用 .ambient 类别音效可以与其他App音乐混合且会被系统中断 // 适合非核心的游戏音频体验 try audioSession.setCategory(.ambient, mode: .default, options: []) try audioSession.setActive(true) } catch { print(“配置失败: \(error)”) }进阶选择如果游戏音频是核心体验如音乐游戏不希望被其他App音乐干扰但可以被系统电话中断可以使用.soloAmbient。如果需要后台播放比如挂机游戏则必须使用.playback并开启后台模式。4. 高级话题与疑难杂症排查配置好了只是第一步AVAudioSession在运行时的动态行为更为复杂。下面这些高级技巧和常见问题是区分普通开发者和音频专家的关键。4.1 音频会话的生命周期与激活/停用setActive(_:)是控制音频会话是否“占用”音频硬件的开关。激活(true)意味着你的App准备开始使用音频系统会为你分配资源停用(false)则释放资源。关键选项.notifyOthersOnDeactivation这是极其重要的一个选项。当你的App停用音频会话时比如播放结束如果激活时传入了这个选项系统会通知之前被你打断的App比如后台音乐App“我现在不用了你们可以恢复了”。这是一个良好的“公民行为”。对于使用.playback或.playAndRecord且会打断其他音频的App在停用时都应该使用它。// 激活时声明停用时会通知他人 try audioSession.setActive(true, options: .notifyOthersOnDeactivation) // ... 播放音频 ... // 播放结束后停用会话 try audioSession.setActive(false) // 系统会自动通知其他被中断的App恢复生命周期管理App启动/进入前台在viewDidLoad或applicationDidBecomeActive中配置并激活会话。App进入后台如果你的App支持后台音频只需保持会话激活状态即可。如果不支持系统可能会自动停用它。音频播放结束如果你不再需要音频硬件比如播放列表播完了应该主动停用会话(setActive(false))并带上.notifyOthersOnDeactivation选项。处理音频中断必须监听AVAudioSession.interruptionNotification。当有来电、闹钟等高优先级事件时你的会话会被系统中断。在中断开始的通知里你应该保存状态并暂停播放在中断结束的通知里检查是否可以恢复根据userInfo中的AVAudioSessionInterruptionOptionKey然后重新激活会话并恢复播放。4.2 音频路由变更与设备管理用户可能随时插入耳机、连接蓝牙音箱、或者切换到车载音响。这些变化会触发AVAudioSession.routeChangeNotification。在通知回调中你需要从userInfo中获取变更原因(AVAudioSessionRouteChangeReasonKey)例如.newDeviceAvailable新设备可用、.oldDeviceUnavailable旧设备不可用、.categoryChange类别变化导致等。根据原因做出响应。例如当耳机被拔出.oldDeviceUnavailable时你应该将音频输出切换到扬声器避免声音外放造成尴尬。可以使用audioSession.currentRoute来获取当前的路由信息。手动覆盖输出端口 你可以使用overrideOutputAudioPort(_:)方法强制切换输出。// 切换到扬声器 try? audioSession.overrideOutputAudioPort(.speaker) // 切换回默认通常是听筒取决于Category和已连接设备 try? audioSession.overrideOutputAudioPort(.none)注意覆盖输出端口是一个临时性操作。当发生下一次路由变更如插入耳机时覆盖会被清除。它常用于实现通话中的“免提”按钮功能。4.3 常见错误排查与调试技巧当你遇到音频不响、录音没声音、蓝牙设备不工作等问题时可以按照以下步骤排查检查Category、Mode、Options设置这是最常见的问题源。用打印语句确认你的设置代码确实被执行了且参数正确。捕获并打印所有Error确保do-catch块已经就位并把错误信息详细打印出来。一个insufficientPriority错误就能告诉你当前有更高优先级的音频在运行。查看当前音频会话状态在调试时可以打印audioSession.category、audioSession.mode、audioSession.isOtherAudioPlaying、audioSession.currentRoute等信息了解音频系统的实时状态。验证后台模式如果后台音频有问题首先去Xcode的Capabilities里确认后台音频模式是否已正确开启。模拟器与真机差异模拟器的音频行为与真机有较大差异尤其是涉及硬件麦克风、听筒、蓝牙的部分。所有音频功能特别是录音和路由相关务必在真机上测试。使用音频路由观察工具在真机调试时可以从屏幕底部上滑出控制中心长按音频卡片可以看到当前音频的输出设备。这是一个快速验证音频是否按预期路由的好方法。检查中断通知确保你已经注册并正确处理了音频中断通知。可能你的音频被电话打断了但App没有处理恢复逻辑。一个典型的排查清单问题现象可能原因检查点播放没声音1. 音频会话未激活2. Category不支持播放如.record3. 输出路由错误如.playAndRecord默认用了听筒4. 音频播放器音量/配置问题1.audioSession.isOtherAudioPlaying2.audioSession.category3.audioSession.currentRoute.outputs4. 播放器volume属性、音频文件本身录音没输入1. 未获取麦克风权限2. Category不支持输入如.playback3. 麦克风被其他App占用1. 检查Privacy - Microphone Usage Description2.audioSession.category3. 尝试重启设备蓝牙耳机无法用于通话1. 未设置.allowBluetooth选项2. 蓝牙耳机不支持HFP/HSP仅支持A2DP1.setCategory的options参数2. 换一个通话耳机测试退到后台音频停止1. 未开启后台音频模式2. Category不支持后台如.ambient3. 后台任务被系统清理1. Xcode项目Capabilities2.audioSession.category3. 查看控制台内存警告日志音频播放时其他App音乐没停1. Category是.ambient2. 在.playback中设置了.mixWithOthers选项1.audioSession.category2.setCategory的options参数5. 性能优化与最佳实践掌握了基础配置和问题排查我们再来看看如何让音频体验更上一层楼这涉及到一些性能优化和细节处理。5.1 延迟与I/O缓冲区优化对于实时性要求高的应用如乐器App、语音聊天音频延迟是致命的。AVAudioSession的IOBufferDuration属性允许你设置硬件I/O缓冲区的时长。更小的缓冲区意味着更低的延迟但会增加CPU负担和耗电。设置方法let audioSession AVAudioSession.sharedInstance() let preferredBufferDuration: TimeInterval 0.005 // 5毫秒这是一个较低的延迟设置 do { try audioSession.setPreferredIOBufferDuration(preferredBufferDuration) } catch { print(“设置缓冲区时长失败: \(error)”) }注意事项setPreferredIOBufferDuration只是一个“建议值”系统会根据硬件能力取一个最接近且可行的值。设置后可以通过audioSession.ioBufferDuration读取实际设置的值。对于绝大多数应用使用系统默认值即可。只有在确实需要极低延迟并且经过测试确认性能可接受时才去调整它。5.2 采样率与声道配置你可以通过setPreferredSampleRate(_:)和setPreferredInputNumberOfChannels(_:)/setPreferredOutputNumberOfChannels(_:)来向系统声明你偏好的音频格式。这有助于减少系统在内部进行采样率转换带来的延迟和音质损失。最佳实践将你的音频引擎如AVAudioEngine或音频单元Audio Unit的格式与AVAudioSession的偏好设置保持一致。例如如果你的App内部处理音频采用44.1kHz采样率、单声道那么就这样设置do { try audioSession.setPreferredSampleRate(44100.0) // 对于语音通话输入输出通常单声道即可 try audioSession.setPreferredInputNumberOfChannels(1) try audioSession.setPreferredOutputNumberOfChannels(1) } catch { print(“设置音频格式偏好失败: \(error)”) } // 然后在配置AVAudioEngine时使用相同的格式 let engine AVAudioEngine() let inputNode engine.inputNode let inputFormat inputNode.inputFormat(forBus: 0) // inputFormat 可能会根据硬件和你设置的偏好进行调整5.3 在多场景App中动态切换配置一个App可能包含多个音频场景比如一个社交App既有语音通话功能又有音乐播放功能。你需要在不同场景间动态切换AVAudioSession的配置。切换策略按需激活/停用当进入语音通话界面时配置为.playAndRecord.voiceChat并激活。当通话结束退出界面时停用当前会话带.notifyOthersOnDeactivation。当进入音乐播放界面时再配置为.playback.spokenAudio并激活。使用setActive(_:with:options:)的.notifyOthersOnDeactivation选项这在切换时至关重要。它确保了当你从一个高优先级音频如通话切换到低优先级音频如播放时之前被你打断的App能得到恢复通知。注意状态保存在切换配置前如果正在播放音频记得先暂停播放器保存播放状态。切换完成后再根据新配置恢复或重新开始。示例代码片段// 切换到通话模式 func switchToCallMode() { do { // 先停用当前会话并通知其他App如果是活跃的 if AVAudioSession.sharedInstance().isOtherAudioPlaying { try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) } // 配置为通话模式 try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .defaultToSpeaker]) try AVAudioSession.sharedInstance().setActive(true) // ... 启动音频引擎进行通话 ... } catch { print(“切换到通话模式失败: \(error)”) } } // 切换回媒体播放模式 func switchToPlaybackMode() { do { // 停用通话会话通知其他App try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) // 配置为播放模式 try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio, options: [.mixWithOthers]) try AVAudioSession.sharedInstance().setActive(true) // ... 恢复音乐播放 ... } catch { print(“切换到播放模式失败: \(error)”) } }5.4 与AVAudioEngine等高级框架的协同如果你使用更现代的AVAudioEngine进行音频处理它与AVAudioSession的配合更加紧密。AVAudioEngine内部会管理一个音频单元图Audio Unit Graph而AVAudioSession管理着这个图与系统硬件交互的“策略”。关键点启动顺序通常先配置并激活AVAudioSession然后再启动AVAudioEngineengine.start()。这确保了音频引擎在正确的硬件配置下运行。格式同步如前所述尽量让AVAudioEngine中各个节点特别是输入输出节点的音频格式与AVAudioSession的偏好设置匹配以减少不必要的格式转换。处理中断AVAudioEngine也提供了自己的中断通知AVAudioEngineConfigurationChangeNotification但它更多关注引擎内部配置变化如音频路由变更。对于系统级的音频中断如来电仍然需要监听AVAudioSession.interruptionNotification并在中断发生时调用engine.pause()中断结束后调用engine.start()。踩过这么多坑之后我的体会是AVAudioSession的配置没有一成不变的“银弹”最佳实践完全取决于你的App想要提供什么样的音频体验。最重要的不是死记硬背某个配置组合而是真正理解Category、Mode、Options每个参数背后的设计意图和系统行为。在开发过程中养成在真机上多场景测试的习惯插拔耳机、连接蓝牙、接打电话、切换后台、锁屏解锁观察你的App音频行为是否符合预期。把错误处理做得细致一些把日志打得更全一些当问题出现时这些信息就是你最得力的调试助手。最后记得善用.notifyOthersOnDeactivation做一个友好的“音频公民”这能让你的App在复杂的系统音频生态中与其他应用和谐共处。