1. 音频焦点请求与释放

音频焦点请求与释放 学习卡片

任务说明

实现一个最小可运行的音频播放器,在开始播放前通过 AudioManager 请求音频焦点,在暂停或停止时释放焦点,并处理焦点丢失(AUDIOFOCUS_LOSS)事件——当其他应用抢走焦点时自动暂停播放。本案例聚焦于焦点管理逻辑本身,不涉及音频解码与渲染细节,使用 ToneGenerator 产生提示音作为可感知的播放状态。

核心实现

// AudioFocusPlayer.java
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.content.Context;

public class AudioFocusPlayer {
    private final AudioManager audioManager;
    private final ToneGenerator toneGenerator;
    private boolean isPlaying = false;

    // 焦点监听器:处理焦点丢失事件
    private final AudioManager.OnAudioFocusChangeListener focusChangeListener =
            new AudioManager.OnAudioFocusChangeListener() {
                @Override
                public void onAudioFocusChange(int focusChange) {
                    switch (focusChange) {
                        case AudioManager.AUDIOFOCUS_LOSS:
                            // 长时间失去焦点:暂停并释放焦点
                            pausePlayback();
                            abandonFocus();
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                            // 短暂失去焦点(如来电):暂停但不释放
                            pausePlayback();
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                            // 可闪避:降低音量(ToneGenerator 不支持闪避,直接忽略)
                            break;
                    }
                }
            };

    public AudioFocusPlayer(Context context) {
        audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        toneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, 80);
    }

    // 请求焦点并开始播放
    public boolean startPlayback() {
        if (isPlaying) return true;

        int result = audioManager.requestAudioFocus(focusChangeListener,
                AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN);

        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            isPlaying = true;
            // 持续播放 1kHz 提示音,模拟播放状态
            toneGenerator.startTone(ToneGenerator.TONE_CDMA_PIP, 1500);
            return true;
        }
        return false; // 焦点请求被拒绝,不播放
    }

    // 暂停播放(保留焦点)
    public void pausePlayback() {
        if (isPlaying) {
            toneGenerator.stopTone();
            isPlaying = false;
        }
    }

    // 停止播放并释放焦点
    public void stopPlayback() {
        pausePlayback();
        abandonFocus();
    }

    // 释放音频焦点
    private void abandonFocus() {
        audioManager.abandonAudioFocus(focusChangeListener);
    }

    // 资源释放
    public void release() {
        stopPlayback();
        toneGenerator.release();
    }
}

调用示例

// 在 Activity 中使用
AudioFocusPlayer player = new AudioFocusPlayer(this);

// 播放:请求焦点,成功后才开始
boolean granted = player.startPlayback();
if (!granted) {
    // 焦点被拒,提示用户当前无法播放
}

// 用户点击暂停
player.pausePlayback();

// 用户点击停止
player.stopPlayback();

// Activity 销毁时释放资源
@Override
protected void onDestroy() {
    super.onDestroy();
    player.release();
}

焦点状态与事件流

本应用播放器 AudioManager 其他应用 requestAudioFocus() AUDIOFOCUS_REQUEST_GRANTED 开始播放 请求焦点 AUDIOFOCUS_LOSS 暂停+释放焦点

验证方法

判定标准:在真机或模拟器上安装运行,依次执行以下操作并观察行为——

  1. 正常播放:调用 startPlayback() 后能听到提示音,说明焦点请求成功且播放已开始;
  2. 焦点被抢占:播放期间打开系统音乐应用并播放歌曲,本应用提示音应立即停止(AUDIOFOCUS_LOSS 触发 pausePlayback());
  3. 焦点释放:调用 stopPlayback() 后,其他应用可正常获得音频焦点(不被本应用阻塞);
  4. 暂停不释放:调用 pausePlayback() 后提示音停止,但焦点仍被持有,此时其他应用请求焦点会触发 AUDIOFOCUS_LOSS 回调。

通过 adb shell dumpsys audio 可查看当前焦点持有者,验证释放逻辑是否正确。