1. 构建音频焦点请求与释放框架

构建音频焦点请求与释放框架 学习卡片

任务说明

在Android音视频应用中,多个播放器可能同时运行(如音乐播放器与导航语音),系统通过音频焦点(Audio Focus)机制协调音频流的使用权。本案例实现一个可复用的音频焦点管理框架,支持请求、释放、监听焦点变化并自动处理短暂丢失与永久丢失两种典型场景。

核心实现

// AudioFocusManager.java
package com.example.audiofocus;

import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

public class AudioFocusManager {
    private static final String TAG = "AudioFocusManager";
    private final AudioManager audioManager;
    private final FocusListener focusListener;
    private AudioFocusRequest focusRequest;
    private boolean hasFocus = false;
    private final Handler mainHandler = new Handler(Looper.getMainLooper());

    // 焦点变化回调接口
    public interface FocusListener {
        void onFocusGained();          // 获得焦点(可开始播放)
        void onFocusLostTransient();   // 短暂丢失(如来电,可暂停)
        void onFocusLostPermanent();   // 永久丢失(如其他应用抢占,应停止)
        void onFocusDucked();          // 闪避(降低音量而非暂停)
    }

    public AudioFocusManager(Context context, FocusListener listener) {
        this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        this.focusListener = listener;
        buildFocusRequest();
    }

    private void buildFocusRequest() {
        AudioAttributes attrs = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                    .setAudioAttributes(attrs)
                    .setAcceptsDelayedFocusGain(true)
                    .setWillPauseWhenDucked(false)
                    .setOnAudioFocusChangeListener(changeListener, mainHandler)
                    .build();
        }
    }

    private final AudioManager.OnAudioFocusChangeListener changeListener =
            new AudioManager.OnAudioFocusChangeListener() {
                @Override
                public void onAudioFocusChange(int focusChange) {
                    switch (focusChange) {
                        case AudioManager.AUDIOFOCUS_GAIN:
                            hasFocus = true;
                            focusListener.onFocusGained();
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                            hasFocus = false;
                            focusListener.onFocusLostTransient();
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS:
                            hasFocus = false;
                            focusListener.onFocusLostPermanent();
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                            focusListener.onFocusDucked();
                            break;
                    }
                }
            };

    /** 请求音频焦点 */
    public boolean requestFocus() {
        if (hasFocus) return true;
        int result;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            result = audioManager.requestAudioFocus(focusRequest);
        } else {
            // 低版本兼容(API 26以下)
            result = audioManager.requestAudioFocus(changeListener,
                    AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
        }
        hasFocus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
        Log.d(TAG, "requestFocus result: " + result);
        return hasFocus;
    }

    /** 释放音频焦点 */
    public void abandonFocus() {
        if (!hasFocus) return;
        int result;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            result = audioManager.abandonAudioFocusRequest(focusRequest);
        } else {
            result = audioManager.abandonAudioFocus(changeListener);
        }
        hasFocus = false;
        Log.d(TAG, "abandonFocus result: " + result);
    }

    public boolean isHasFocus() {
        return hasFocus;
    }
}

使用示例

// MainActivity.java 关键片段
AudioFocusManager focusManager = new AudioFocusManager(this, new AudioFocusManager.FocusListener() {
    @Override
    public void onFocusGained() {
        // 开始或恢复播放
        mediaPlayer.start();
    }

    @Override
    public void onFocusLostTransient() {
        // 暂停播放,等待重新获得焦点
        mediaPlayer.pause();
    }

    @Override
    public void onFocusLostPermanent() {
        // 停止播放并释放资源
        mediaPlayer.stop();
        mediaPlayer.release();
    }

    @Override
    public void onFocusDucked() {
        // 降低音量至20%
        mediaPlayer.setVolume(0.2f, 0.2f);
    }
});

// 用户点击播放按钮时
focusManager.requestFocus();
// 用户点击停止或退出时
focusManager.abandonFocus();

验证方式

在真机上安装应用,准备两个音频播放源(如系统音乐播放器与本应用)。操作步骤:

  1. 启动本应用并点击播放,确认日志输出requestFocus result: 1(1表示AUDIOFOCUS_REQUEST_GRANTED),声音正常播放;
  2. 启动系统音乐播放器并播放,本应用应收到onFocusLostPermanent回调,播放停止;
  3. 关闭系统音乐播放器,再次点击本应用播放,应重新获得焦点并恢复播放;
  4. 在应用播放期间来电(或使用adb模拟短暂焦点丢失),应收到onFocusLostTransient回调并暂停,挂断后自动恢复。

判定标准:以上4个场景中回调触发顺序与日志输出符合预期,且无崩溃或ANR,即验证通过。

音频焦点请求与释放时序 应用播放器 AudioFocusManager AudioManager requestFocus() 调用 requestAudioFocus() GRANTED (1) 返回 onFocusGained() 播放中... LOSS_TRANSIENT onFocusLostTransient() 暂停播放 AUDIOFOCUS_GAIN onFocusGained() 恢复播放 abandonFocus() abandonAudioFocus() 成功/授权 焦点丢失 释放