音乐app笔记

https://www.doubao.com/chat/35685426188293122

安卓开发 AudioManager.AUDIOFOCUS_GAIN AudioManager.AUDIOFOCUS_LOSS, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 有啥区别

你想弄清楚安卓开发中 AudioManager 类里这几个音频焦点相关常量的具体区别,以及它们各自对应的使用场景,这是安卓音频开发中核心且基础的知识点。

首先要理解核心概念:音频焦点(Audio Focus) 是安卓系统用来协调多个应用播放音频的机制 ------ 同一时间只有一个应用能获得 "完整" 的音频焦点,其他应用需根据焦点变化做出对应调整。下面逐个解释这些常量的区别:

1. AudioManager.AUDIOFOCUS_GAIN

  • 含义获取长期、稳定的音频焦点
  • 使用场景:当你的应用需要持续播放音频(无明确时长限制)时申请,比如音乐播放器、播客 APP 开始播放音频时。
  • 行为:申请到这个焦点后,你的应用可以正常、高音量播放音频,其他应用通常会失去焦点并做出相应处理。

2. AudioManager.AUDIOFOCUS_LOSS

  • 含义永久性失去音频焦点
  • 触发场景:比如用户打开另一个音乐 APP 并开始播放,系统会把焦点永久分配给新 APP,你的应用收到这个状态。
  • 处理要求 :必须停止播放音频,并释放相关音频资源(比如暂停播放、关闭播放器);如果想恢复播放,需要用户手动操作(比如点击播放按钮),因为焦点不会自动返回。

3. AudioManager.AUDIOFOCUS_LOSS_TRANSIENT

  • 含义临时性失去音频焦点(短时间内会恢复)。
  • 触发场景:比如来电铃声、短信提示音、导航的短暂语音播报(如 "已到达目的地")。
  • 处理要求暂停播放音频,但保留播放状态(比如记住当前播放进度);当焦点恢复时,无需用户操作即可自动继续播放。

4. AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

  • 含义临时性失去焦点,但允许 "降音(Ducking)"(而非完全暂停)。
  • 触发场景:比如导航的持续轻声提示(如 "前方 100 米左转")、微信语音消息播放。
  • 处理要求 :不需要暂停播放,只需降低音频音量(通常降到原音量的 20%-30%);当焦点恢复后,再把音量调回正常水平即可。
  • 补充:"Ducking" 是音频领域的术语,就是 "压低音量、混响" 的意思,核心是让你的音频和临时提示音共存,且不影响用户听提示音。

直观示例对比

常量 核心行为 典型场景 处理方式
AUDIOFOCUS_GAIN 获取长期焦点 音乐 APP 播放歌曲 正常高音量播放
AUDIOFOCUS_LOSS 永久失焦 切到另一个音乐 APP 停止播放,释放资源
AUDIOFOCUS_LOSS_TRANSIENT 临时失焦 来电铃声、短语音提示 暂停播放,等待恢复
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 临时失焦(允许降音) 导航轻声提示
  • AUDIOFOCUS_GAIN获取长期音频焦点,用于持续播放音频的场景;
  • AUDIOFOCUS_LOSS永久失焦,需停止播放并释放资源;
  • 两个 TRANSIENT 类都是临时失焦LOSS_TRANSIENT 需暂停播放,LOSS_TRANSIENT_CAN_DUCK 只需降低音量即可。
Kotlin 复制代码
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import java.io.IOException;

public class AudioFocusActivity extends AppCompatActivity {
    private AudioManager audioManager;
    private MediaPlayer mediaPlayer;
    // 音频焦点变更监听器(核心:处理失焦回调)
    private AudioManager.OnAudioFocusChangeListener focusChangeListener = focusChange -> {
        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                // 重新获取焦点:恢复播放+调回正常音量
                if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
                    mediaPlayer.start();
                    mediaPlayer.setVolume(1.0f, 1.0f);
                }
                break;
            case AudioManager.AUDIOFOCUS_LOSS:
                // 永久失焦:停止播放+释放资源
                releaseAudioResources();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                // 临时失焦:暂停播放(保留进度)
                if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                    mediaPlayer.pause();
                }
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                // 临时失焦可降音:降低音量
                if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                    mediaPlayer.setVolume(0.2f, 0.2f); // 降到20%
                }
                break;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_audio_focus);
        
        // 初始化AudioManager
        audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        
        // 示例:播放音频按钮(点击后申请焦点+播放)
        Button playBtn = findViewById(R.id.btn_play);
        playBtn.setOnClickListener(v -> playAudio());
        
        // 示例:停止音频按钮(点击后释放焦点+停止)
        Button stopBtn = findViewById(R.id.btn_stop);
        stopBtn.setOnClickListener(v -> stopAudio());
    }

    // 播放音频:先申请焦点,成功后再播放
    private void playAudio() {
        // 1. 申请音频焦点(以"导航单次播报"为例,用GAIN_TRANSIENT)
        int focusResult = audioManager.requestAudioFocus(
            focusChangeListener,          // 失焦回调监听器
            AudioManager.STREAM_MUSIC,     // 音频流类型(音乐流)
            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT // 申请的焦点类型
        );

        // 2. 焦点申请成功,才初始化并播放音频
        if (focusResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            mediaPlayer = new MediaPlayer();
            try {
                // 替换为你的音频文件路径(本地/网络)
                mediaPlayer.setDataSource("your_audio_path.mp3");
                mediaPlayer.prepare();
                mediaPlayer.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 停止音频:释放焦点+释放音频资源
    private void stopAudio() {
        // 1. 释放音频焦点
        audioManager.abandonAudioFocus(focusChangeListener);
        // 2. 释放MediaPlayer资源
        releaseAudioResources();
    }

    // 释放音频资源的工具方法
    private void releaseAudioResources() {
        if (mediaPlayer != null) {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.stop();
            }
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }

    // 页面销毁时,确保释放所有资源
    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopAudio();
    }
}
  1. 申请与释放必须配对 :申请焦点后,音频播放结束 / 页面销毁时,一定要调用 abandonAudioFocus() 释放,否则其他应用无法恢复焦点;
  2. 流类型要匹配 :申请焦点时指定的 STREAM_* 要和你的音频类型一致(比如音乐用 STREAM_MUSIC,通话用 STREAM_VOICE_CALL);
  3. 容错处理 :申请焦点可能失败(返回 AUDIOFOCUS_REQUEST_FAILED),需做容错,不要直接播放;
  4. 适配安卓版本 :Android 8.0(API 26)后新增了 AudioFocusRequest 类,更灵活,但上述基础常量的逻辑完全兼容。

总结

  1. 音频焦点分申请(GAIN)失焦回调(LOSS) 两类,GAIN 类型决定其他应用收到的 LOSS 状态;
  2. 长期播放用 AUDIOFOCUS_GAIN,短期播放根据是否允许其他降音 / 是否独占,选择 TRANSIENT/TRANSIENT_MAY_DUCK/TRANSIENT_EXCLUSIVE
  3. 失焦时:永久失焦需停止并释放资源,临时失焦暂停,允许降音则仅调低音量,焦点恢复后对应恢复播放 / 音量。

MediaSession

MediaSession 是 Android 系统提供的媒体会话管理类 ,隶属于 android.media.session 包,核心作用是统一管理媒体播放状态、控制指令和元数据 ,实现应用内播放控制跨组件 / 设备的媒体交互

https://www.doubao.com/chat/35755426884014850

比如按键后结果命令执行下一首 上一首 暂停 播放

Kotlin 复制代码
// 媒体会话回调:处理控制指令
    private val mediaSessionCallback = object : MediaSessionCompat.Callback() {
        // 播放指令
        override fun onPlay() {
            super.onPlay()
            exoPlayer.play()
            // 更新播放状态到 MediaSession
            updatePlaybackState(PlaybackStateCompat.STATE_PLAYING)
        }

        // 暂停指令
        override fun onPause() {
            super.onPause()
            exoPlayer.pause()
            updatePlaybackState(PlaybackStateCompat.STATE_PAUSED)
        }

        // 下一曲
        override fun onSkipToNext() {
            super.onSkipToNext()
            // 切换到下一首歌曲逻辑
            playNextSong()
        }

        // 上一曲
        override fun onSkipToPrevious() {
            super.onSkipToPrevious()
            playPreviousSong()
        }
    }

如果不用这个mediasession会有什么问题

如果不使用 MediaSession 实现安卓媒体播放功能,会面临系统级交互缺失、多端控制适配复杂、音频焦点管理混乱等一系列问题,具体可以分为以下几类:

一、系统级播放控制完全失效

MediaSession 是应用与安卓系统媒体服务的桥梁,不使用它的话,系统自带的播放控制入口会全部无法响应:

  1. 通知栏 / 锁屏控件无法使用
    • 无法在通知栏、锁屏界面显示播放 / 暂停、上一曲 / 下一曲按钮,用户必须打开应用才能操作播放状态,体验极差。
    • 无法在通知栏展示歌曲标题、歌手、封面等元数据,失去核心的信息展示入口。
  2. 硬件按键控制失效
    • 耳机线控、蓝牙音箱 / 耳机的播放键、音量键(部分场景)无法触发播放 / 暂停操作。
    • 车载系统(Android Auto)、智能手表等外接设备无法识别应用的播放状态,更无法进行远程控制。
  3. 系统媒体中心不识别应用
    • 安卓系统的全局媒体控制中心(如下拉状态栏的媒体卡片)不会显示你的应用,用户无法在多应用间快速切换播放控制。

二、音频焦点管理混乱,易出现冲突

安卓系统的音频焦点(Audio Focus) 是避免多个应用同时播放音频的核心机制,MediaSession 会自动配合 AudioManager 处理焦点逻辑,不使用它则需要手动实现所有细节,且极易出错:

  1. 无法处理焦点抢占
    • 来电、闹钟响起、其他音乐应用启动时,你的应用不会收到焦点丢失通知,会继续播放音频,造成声音重叠。
    • 焦点恢复后(如通话结束),无法自动继续播放,需要用户手动操作。
  2. 手动管理成本高且易遗漏
    • 即使自己调用 AudioManager 请求 / 放弃焦点,也无法和系统播放状态联动(比如焦点丢失时,通知栏控件不会同步变为暂停状态)。

三、多组件 / 多进程通信复杂

在大型媒体应用中,播放逻辑通常放在 Service (后台播放),而控制界面在 Activity/Fragment ,不使用 MediaSession 会面临通信难题:

相关推荐
am心2 小时前
学习笔记-菜品接口-菜品分页查询
笔记·学习
swan4162 小时前
SCAU期末笔记 - 计算机网络雨课堂习题整理
arm开发·笔记·计算机网络
丝斯20112 小时前
AI学习笔记整理(44)——大规模预训练模型数据处理管道Pipeline
人工智能·笔记·学习
自不量力的A同学2 小时前
Dragonfly v2.4.0 发布
笔记
代码游侠3 小时前
学习笔记——51单片机学习
笔记·stm32·单片机·嵌入式硬件·51单片机
博览鸿蒙4 小时前
FPGA 开发软件学习笔记分享(内含安装与环境配置)
笔记·学习·fpga开发
日更嵌入式的打工仔5 小时前
SxsTrace.exe使用笔记
笔记
其美杰布-富贵-李5 小时前
pycalphad 学习笔记
笔记·学习·热力学计算
宵时待雨5 小时前
数据结构(初阶)笔记归纳2:顺序表的实现
c语言·数据结构·笔记·算法