Android 17 新特性:后台音频交互限制加强

大家好,我是拭心。

Android 17 对后台音频交互引入了新的限制,这个变更分两层:第一层对所有应用 生效(无论 targetSdk),第二层对 target 37 的应用更严格。

如果你的应用涉及后台音频播放、音频焦点管理,有必要了解一下这次变更的边界和应对方案。

这篇文章我们来了解下这两层限制。

一、为什么要加这个限制?

在说"限制了什么"之前,先说说"为什么要限"------因为现实中确实存在三类让用户头疼的"幽灵音频"场景。

场景一:冻结后意外恢复播放。 用户切走去做别的事,音乐 App 被系统冻结。数小时后系统解冻 App,音频意外恢复播放,用户完全不知情。

场景二:断断续续的后台音频。 App 没有前台服务,受到后台运行限制,音频播放时断时续,体验极差。

场景三:泄漏的播放会话。 Activity 销毁时播放没有正确停止,音频焦点泄漏,其他 App 无法获取焦点,用户按下暂停也没用。

这三类问题的根源都是"App 在用户不知情的情况下进行音频操作"。Android 17 的限制,本质上是让系统从制度层面要求 App 证明自己的音频行为是"用户授权的"。

二、两层限制详解

第一层:适用于所有应用

无论 targetSdk 是多少,只要应用在后台进行音频交互(播放、请求音频焦点、调整音量),就必须满足以下条件之一:

  • 有可见的 Activity(应用在前台)
  • 运行了SHORT_SERVICE 类型的前台服务(Foreground Service)

不满足条件时:

  • 音频播放、音量调整 API → 静默失败(不抛异常,但操作不生效)
  • 音频焦点请求 → 返回 AUDIOFOCUS_REQUEST_FAILED

"静默失败"是个需要特别注意的点。没有异常抛出,意味着你如果不主动检测返回值,代码看起来跑通了,但音频压根没有播。

第二层:仅适用于 target 37 应用

在第一层的基础上,target 37 的应用有额外要求:前台服务必须具备 WIU(While-In-Use)能力

什么是 WIU 能力?前台服务在以下情况下会被系统授予 WIU 能力:

  • 在用户发起的操作中启动(比如用户点击播放按钮)
  • 在应用对用户可见时启动

简单来说:FGS 必须由用户主动触发,而不是 App 自己偷偷启动

复制代码
所有应用(第一层):
后台音频 → 必须有非 SHORT_SERVICE 类型的 FGS,或有可见 Activity

target 37(第二层,更严):
后台音频 → 必须有 WIU-capable FGS
(FGS 在用户操作时或应用可见时启动)

有一个豁免情况:如果应用持有 MODIFY_AUDIO_SETTINGS 权限,且修改的是带有 FLAG_AUDIBILITY_ENFORCED 属性的音频流,则不受 WIU 要求限制。

三、正确的后台音频架构

官方推荐方案是 Media3 + MediaSessionService。这套方案不仅满足了新限制的要求,还自动解决了通知栏控制、锁屏控制、Android Auto 集成等一系列问题。

3.1 定义 MediaSessionService

kotlin 复制代码
class MusicPlaybackService : MediaSessionService() {
    private lateinit var player: ExoPlayer
    private lateinit var mediaSession: MediaSession

    override fun onCreate() {
        super.onCreate()
        player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    override fun onGetSession(
        controllerInfo: MediaSession.ControllerInfo
    ): MediaSession = mediaSession

    override fun onDestroy() {
        // 必须释放资源,否则可能导致粘性通知无法消除
        mediaSession.release()
        player.release()
        super.onDestroy()
    }
}

在 Manifest 里声明,指定 foregroundServiceTypemediaPlayback

xml 复制代码
<service
    android:name=".MusicPlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService" />
    </intent-filter>
</service>

3.2 关键:在用户操作时启动 FGS

这是 target 37 的核心要求------FGS 必须在应用可见、用户主动操作时启动,才能获得 WIU 能力:

kotlin 复制代码
// ✅ 正确:在用户点击播放时启动(应用可见 → 获得 WIU 能力)
class PlayerActivity : AppCompatActivity() {
    fun onPlayButtonClick() {
        val intent = Intent(this, MusicPlaybackService::class.java)
        ContextCompat.startForegroundService(this, intent)

        val sessionToken = SessionToken(
            this, ComponentName(this, MusicPlaybackService::class.java)
        )
        val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
        controllerFuture.addListener({
            controllerFuture.get().play()
        }, MoreExecutors.directExecutor())
    }
}
kotlin 复制代码
// ❌ 错误:在 BOOT_COMPLETE 中启动并播放(非用户发起,无 WIU 能力)
class BootReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
            // Android 17 下,后续的音频操作会被静默阻止
            context.startForegroundService(
                Intent(context, MusicPlaybackService::class.java)
            )
        }
    }
}

3.3 瞬时中断时保持 FGS 活跃

来电、其他 App 请求焦点等瞬时中断,不要停止 FGS,否则恢复播放时 WIU 能力已失效:

kotlin 复制代码
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
    when (focusChange) {
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            player.pause()
            // ✅ 不要 stopSelf(),保持 FGS 活跃
        }
        AudioManager.AUDIOFOCUS_LOSS -> {
            player.stop()
            stopSelf() // 永久失去焦点才停止 FGS
        }
        AudioManager.AUDIOFOCUS_GAIN -> {
            player.play()
        }
    }
}

3.4 主动检测音频焦点失败

由于播放和音量 API 是静默失败的,音频焦点请求是唯一有明确返回值的入口,一定要检测:

kotlin 复制代码
val result = audioManager.requestAudioFocus(
    AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setOnAudioFocusChangeListener(audioFocusChangeListener)
        .build()
)

when (result) {
    AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
        player.play()
    }
    AudioManager.AUDIOFOCUS_REQUEST_FAILED -> {
        // 后台无 WIU FGS 时会走到这里
        // 可以提示用户"请先返回应用再播放",或者等应用回到前台再重试
        Log.w("Audio", "音频焦点请求失败,当前可能在后台且无 WIU FGS")
    }
    AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> {
        // 延迟授予,在 OnAudioFocusChangeListener 里等待 AUDIOFOCUS_GAIN
    }
}

四、迁移检查清单

检查项 风险 处理方式
使用 MediaPlayer + 普通 Service 后台播放 迁移到 Media3 + MediaSessionService
FGS 在 BOOT_COMPLETE 或定时任务中启动 改为用户操作触发
未检测 AUDIOFOCUS_REQUEST_FAILED 添加失败处理逻辑
使用 SHORT_SERVICE 类型 FGS 改为 mediaPlayback 类型
瞬时中断时调用了 stopSelf() 改为只在永久失去焦点时停止 FGS
播放结束未停止 FGS 监听 Player.STATE_ENDED,播完停止 FGS

总结

Android 17 后台音频限制的核心逻辑:系统要求 App 证明自己的音频行为是用户授权的。两层限制的关键区别:

  • 所有应用:后台音频必须有 FGS(非 SHORT_SERVICE),否则静默失败
  • target 37:FGS 还必须具备 WIU 能力,即在用户操作时或应用可见时启动

迁移方向是 Media3 + MediaSessionService------这不只是为了满足限制,MediaSessionService 还自动处理了通知栏控制、锁屏播放控制等一系列后台播放的配套能力,是一次"被迫的架构升级",升完之后反而更省心。

好了,这篇文章到这里就结束了,感谢你的阅读,愿你平安顺遂。

如果对你有帮助,欢迎评论点赞转发,你的支持是我最大的动力❤️

相关推荐
张拭心1 小时前
Android 17 新特性:ProfilingManager 新触发器
android·前端
weixin_471383032 小时前
Taro-03-页面生命周期
前端·javascript·taro
张拭心2 小时前
Android 17 新特性:MessageQueue 无锁实现
android·前端
brycegao2 小时前
如何搭建标准化 Git 工具流,保障 Android 团队代码质量
android·ci/cd
Asize2 小时前
数组数据结构底层:从灵活到陷阱
前端·javascript·算法
AI科技星2 小时前
数术江湖·全卷合集 - 硬核江湖・数理史诗
android·人工智能·架构·概率论·学习方法
十九画生2 小时前
Ajax 入门:用 XHR 理解前后端异步请求
前端·javascript·后端
yingyima2 小时前
Python re 模块速查:从实战对比中掌握正则表达式
前端
五月君_2 小时前
安卓也支持了!微信链接 Claude Code 保姆级教程
android·微信