Android 中音频焦点的使用场景及代码示例
- 一、音频焦点简介
 - 二、使用场景
 - 三、版本兼容性处理
 - 四、音频焦点与 MediaSession 结合使用
 - 五、音频焦点在前台服务中的使用
 - 六、ExoPlayer 中的音频焦点处理
 - 七、音频焦点与蓝牙设备交互
 - 八、音频焦点请求失败的处理策略
 - 九、最佳实践与注意事项
 
一、音频焦点简介
在 Android 系统中,音频焦点(Audio Focus)是一种机制,用于管理多个应用程序同时播放音频时的冲突。当一个应用程序请求音频焦点并获得它时,其他应用程序在播放音频时需要做出相应的调整,以避免多个音频同时播放造成混乱。

二、使用场景
(一)音乐播放器
- 当用户正在使用音乐播放器收听歌曲时,若有来电,音乐播放器应暂停播放,以让电话铃声能够清晰地被听到。当电话结束后,音乐播放器可以根据情况恢复播放。
 - 若用户在听音乐的过程中打开了另一个音乐类应用,此时正在播放音乐的应用应该暂停或降低音量,以避免两个音乐同时播放。
 
(二)语音导航应用
- 在用户使用语音导航的同时,如果有音乐播放,导航的语音提示应该能够优先播放,确保用户能够清楚地听到导航指令。
 - 当导航语音提示结束后,音乐可以恢复正常播放。
 
(三)社交类应用的语音消息
- 当用户在收听社交类应用的语音消息时,若有其他音频正在播放,应该暂停或降低其他音频的音量,以便用户能够听清语音消息。
 - 语音消息播放完毕后,其他音频可以恢复播放。
 
三、版本兼容性处理
(一)Android 8.0 及以上版本
在 Android 8.0(API 26)及以上版本中,音频焦点的请求方式发生了变化,推荐使用 AudioFocusRequest 来构建请求:
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
public class AudioFocusHelper {
    private AudioManager audioManager;
    private AudioFocusRequest focusRequest;
    private OnAudioFocusChangeListener focusChangeListener;
    public interface OnAudioFocusChangeListener {
        void onAudioFocusChange(int focusChange);
    }
    public AudioFocusHelper(Context context, OnAudioFocusChangeListener listener) {
        audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        this.focusChangeListener = listener;
    }
    public boolean requestAudioFocus() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            AudioAttributes playbackAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();
            focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                .setAudioAttributes(playbackAttributes)
                .setAcceptsDelayedFocusGain(true)
                .setOnAudioFocusChangeListener(focusChangeListener::onAudioFocusChange)
                .build();
            int result = audioManager.requestAudioFocus(focusRequest);
            return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
        } else {
            int result = audioManager.requestAudioFocus(focusChangeListener::onAudioFocusChange,
                AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN);
            return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
        }
    }
    public void abandonAudioFocus() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (focusRequest != null) {
                audioManager.abandonAudioFocusRequest(focusRequest);
            }
        } else {
            audioManager.abandonAudioFocus(focusChangeListener::onAudioFocusChange);
        }
    }
}
四、音频焦点与 MediaSession 结合使用
在现代 Android 应用中,推荐将音频焦点与 MediaSession 结合使用,以提供更好的媒体控制体验。以下是使用 AndroidX 库的更新版本:
import android.content.Context;
import android.media.AudioManager;
import androidx.media.session.MediaSessionCompat;
import androidx.media.app.NotificationCompat;
import androidx.media.session.PlaybackStateCompat;
public class MediaPlaybackManager {
    private MediaSessionCompat mediaSession;
    private AudioFocusHelper audioFocusHelper;
    private boolean playOnAudioFocus = false;
    private Context context;
    public MediaPlaybackManager(Context context) {
        this.context = context;
        audioFocusHelper = new AudioFocusHelper(context, this::onAudioFocusChange);
        mediaSession = new MediaSessionCompat(context, "MediaPlaybackManager");
        setupMediaSession();
    }
    private void setupMediaSession() {
        mediaSession.setCallback(new MediaSessionCompat.Callback() {
            @Override
            public void onPlay() {
                if (audioFocusHelper.requestAudioFocus()) {
                    mediaSession.setActive(true);
                    // 开始播放
                    startPlayback();
                } else {
                    playOnAudioFocus = true;
                }
            }
            @Override
            public void onPause() {
                // 暂停播放
                pausePlayback();
            }
            @Override
            public void onStop() {
                audioFocusHelper.abandonAudioFocus();
                mediaSession.setActive(false);
                // 停止播放
                stopPlayback();
            }
        });
        // 设置播放状态
        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
            .setActions(PlaybackStateCompat.ACTION_PLAY | 
                       PlaybackStateCompat.ACTION_PAUSE | 
                       PlaybackStateCompat.ACTION_PLAY_PAUSE |
                       PlaybackStateCompat.ACTION_STOP)
            .setState(PlaybackStateCompat.STATE_NONE, 0, 1.0f)
            .build();
        mediaSession.setPlaybackState(state);
    }
    private void onAudioFocusChange(int focusChange) {
        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                if (playOnAudioFocus) {
                    playOnAudioFocus = false;
                    startPlayback();
                }
                // 恢复音量
                break;
            case AudioManager.AUDIOFOCUS_LOSS:
                playOnAudioFocus = false;
                stopPlayback();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                playOnAudioFocus = true;
                pausePlayback();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                // 降低音量
                break;
        }
    }
    private void startPlayback() {
        // 实现播放逻辑
        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
            .setState(PlaybackStateCompat.STATE_PLAYING, 0, 1.0f)
            .build();
        mediaSession.setPlaybackState(state);
    }
    private void pausePlayback() {
        // 实现暂停逻辑
        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
            .setState(PlaybackStateCompat.STATE_PAUSED, 0, 1.0f)
            .build();
        mediaSession.setPlaybackState(state);
    }
    private void stopPlayback() {
        // 实现停止逻辑
        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
            .setState(PlaybackStateCompat.STATE_STOPPED, 0, 1.0f)
            .build();
        mediaSession.setPlaybackState(state);
    }
}
五、音频焦点在前台服务中的使用
对于需要在后台持续播放音频的应用,应该在前台服务中处理音频焦点。以下是使用 AndroidX 和通知渠道的更新版本:
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
import androidx.media.app.NotificationCompat.MediaStyle;
public class AudioPlaybackService extends Service {
    private static final int NOTIFICATION_ID = 1;
    private static final String CHANNEL_ID = "audio_playback_channel";
    private AudioFocusHelper audioFocusHelper;
    private MediaPlaybackManager mediaPlaybackManager;
    private NotificationManager notificationManager;
    @Override
    public void onCreate() {
        super.onCreate();
        audioFocusHelper = new AudioFocusHelper(this, this::onAudioFocusChange);
        mediaPlaybackManager = new MediaPlaybackManager(this);
        notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        createNotificationChannel();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (audioFocusHelper.requestAudioFocus()) {
            startForeground(NOTIFICATION_ID, createNotification());
            // 开始播放
            return START_STICKY;
        }
        return START_NOT_STICKY;
    }
    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                "音频播放",
                NotificationManager.IMPORTANCE_LOW);
            channel.setDescription("显示音频播放控制");
            notificationManager.createNotificationChannel(channel);
        }
    }
    private Notification createNotification() {
        // 创建点击通知时的Intent
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
        // 创建媒体控制按钮的Intent
        Intent pauseIntent = new Intent(this, AudioPlaybackService.class);
        pauseIntent.setAction("ACTION_PAUSE");
        PendingIntent pausePendingIntent = PendingIntent.getService(this, 0, pauseIntent, 0);
        // 创建通知
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("正在播放音乐")
            .setContentText("歌曲名称 - 艺术家")
            .setSmallIcon(R.drawable.ic_music_note)
            .setContentIntent(pendingIntent)
            .addAction(R.drawable.ic_pause, "暂停", pausePendingIntent)
            .setStyle(new MediaStyle()
                .setShowActionsInCompactView(0)
                .setMediaSession(mediaPlaybackManager.getMediaSessionToken()))
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setOngoing(true);
        return builder.build();
    }
    @Override
    public void onDestroy() {
        audioFocusHelper.abandonAudioFocus();
        stopForeground(true);
        super.onDestroy();
    }
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    private void onAudioFocusChange(int focusChange) {
        // 处理音频焦点变化
    }
}
六、ExoPlayer 中的音频焦点处理
ExoPlayer 是 Android 推荐的现代媒体播放器库,它内置了对音频焦点的支持。以下是 ExoPlayer 与音频焦点结合使用的示例:
import android.content.Context;
import android.media.AudioManager;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.upstream.DefaultLoadControl;
import androidx.media3.ui.PlayerView;
@UnstableApi
public class ExoPlayerAudioFocusManager implements Player.Listener, AudioManager.OnAudioFocusChangeListener {
    private final ExoPlayer player;
    private final AudioManager audioManager;
    private final AudioFocusRequest audioFocusRequest;
    private boolean playOnFocusGain;
    private final Context context;
    public ExoPlayerAudioFocusManager(Context context, PlayerView playerView) {
        this.context = context;
        audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        // 创建ExoPlayer实例
        DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
        DefaultLoadControl loadControl = new DefaultLoadControl();
        DefaultDataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(context);
        player = new ExoPlayer.Builder(context)
            .setTrackSelector(trackSelector)
            .setLoadControl(loadControl)
            .build();
        playerView.setPlayer(player);
        player.addListener(this);
        // 设置音频属性
        AudioAttributes audioAttributes = new AudioAttributes.Builder()
            .setUsage(C.USAGE_MEDIA)
            .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
            .build();
        player.setAudioAttributes(audioAttributes, true);
        // 创建音频焦点请求
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            android.media.AudioAttributes playbackAttributes = new android.media.AudioAttributes.Builder()
                .setUsage(android.media.AudioAttributes.USAGE_MEDIA)
                .setContentType(android.media.AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();
            audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                .setAudioAttributes(playbackAttributes)
                .setAcceptsDelayedFocusGain(true)
                .setOnAudioFocusChangeListener(this)
                .build();
        } else {
            audioFocusRequest = null;
        }
    }
    public void play(String mediaUrl) {
        // 准备媒体源
        MediaItem mediaItem = MediaItem.fromUri(mediaUrl);
        player.setMediaItem(mediaItem);
        player.prepare();
        // 请求音频焦点
        if (requestAudioFocus()) {
            player.play();
        } else {
            playOnFocusGain = true;
        }
    }
    public void pause() {
        player.pause();
    }
    public void release() {
        abandonAudioFocus();
        player.release();
    }
    private boolean requestAudioFocus() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O && audioFocusRequest != null) {
            int result = audioManager.requestAudioFocus(audioFocusRequest);
            return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
        } else {
            int result = audioManager.requestAudioFocus(
                this, 
                AudioManager.STREAM_MUSIC, 
                AudioManager.AUDIOFOCUS_GAIN);
            return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
        }
    }
    private void abandonAudioFocus() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O && audioFocusRequest != null) {
            audioManager.abandonAudioFocusRequest(audioFocusRequest);
        } else {
            audioManager.abandonAudioFocus(this);
        }
    }
    @Override
    public void onAudioFocusChange(int focusChange) {
        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                if (playOnFocusGain) {
                    playOnFocusGain = false;
                    player.setVolume(1.0f);
                    player.play();
                }
                break;
            case AudioManager.AUDIOFOCUS_LOSS:
                playOnFocusGain = false;
                player.pause();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                playOnFocusGain = true;
                player.pause();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                // 降低音量而不是暂停
                player.setVolume(0.3f);
                break;
        }
    }
    @Override
    public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
        if (playWhenReady) {
            requestAudioFocus();
        } else {
            abandonAudioFocus();
        }
    }
    @Override
    public void onIsPlayingChanged(boolean isPlaying) {
        // 处理播放状态变化
    }
}
七、音频焦点与蓝牙设备交互
当应用通过蓝牙设备播放音频时,需要特别处理音频焦点与蓝牙设备的交互:
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
public class BluetoothAudioManager {
    private final Context context;
    private final AudioManager audioManager;
    private final BluetoothAdapter bluetoothAdapter;
    private final AudioFocusHelper audioFocusHelper;
    private boolean isBluetoothConnected = false;
    private boolean wasPlayingBeforeDisconnect = false;
    public BluetoothAudioManager(Context context, AudioFocusHelper audioFocusHelper) {
        this.context = context;
        this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        this.audioFocusHelper = audioFocusHelper;
        registerBluetoothReceiver();
    }
    private void registerBluetoothReceiver() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        context.registerReceiver(bluetoothReceiver, filter);
    }
    private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
                int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_DISCONNECTED);
                switch (state) {
                    case BluetoothA2dp.STATE_CONNECTED:
                        onBluetoothConnected();
                        break;
                    case BluetoothA2dp.STATE_DISCONNECTED:
                        onBluetoothDisconnected();
                        break;
                }
            }
        }
    };
    private void onBluetoothConnected() {
        isBluetoothConnected = true;
        // 检查是否应该自动恢复播放
        if (wasPlayingBeforeDisconnect) {
            if (audioFocusHelper.requestAudioFocus()) {
                // 恢复播放
                startPlayback();
                wasPlayingBeforeDisconnect = false;
            }
        }
    }
    private void onBluetoothDisconnected() {
        isBluetoothConnected = false;
        // 如果正在播放,标记为应该在重新连接后恢复
        if (isCurrentlyPlaying()) {
            wasPlayingBeforeDisconnect = true;
            pausePlayback();
        }
    }
    public boolean isBluetoothAudioDevice() {
        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
            return false;
        }
        // 检查当前音频输出是否为蓝牙设备
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
            for (AudioDeviceInfo device : devices) {
                if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO ||
                    device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
                    return true;
                }
            }
        } else {
            // 兼容旧版本
            return audioManager.isBluetoothA2dpOn() || audioManager.isBluetoothScoOn();
        }
        return false;
    }
    public void handleBluetoothSco() {
        // 对于需要SCO连接的应用(如语音通话)
        if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) {
            if (!audioManager.isBluetoothScoOn()) {
                audioManager.startBluetoothSco();
                audioManager.setBluetoothScoOn(true);
            }
        }
    }
    public void releaseBluetoothSco() {
        if (audioManager.isBluetoothScoOn()) {
            audioManager.stopBluetoothSco();
            audioManager.setBluetoothScoOn(false);
        }
    }
    public void release() {
        context.unregisterReceiver(bluetoothReceiver);
        releaseBluetoothSco();
    }
    // 以下方法应由应用实现
    private boolean isCurrentlyPlaying() {
        // 返回当前播放状态
        return false;
    }
    private void startPlayback() {
        // 实现开始播放逻辑
    }
    private void pausePlayback() {
        // 实现暂停播放逻辑
    }
}
八、音频焦点请求失败的处理策略
在实际应用中,音频焦点请求可能会失败,我们需要有合适的处理策略:
- 
立即重试策略:
public boolean requestAudioFocusWithRetry() { int maxRetries = 3; int retryCount = 0; while (retryCount < maxRetries) { if (audioFocusHelper.requestAudioFocus()) { return true; } retryCount++; try { Thread.sleep(100); // 等待100毫秒后重试 } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } return false; } - 
延迟重试策略:
private void scheduleAudioFocusRetry() { Handler handler = new Handler(Looper.getMainLooper()); handler.postDelayed(() -> { if (audioFocusHelper.requestAudioFocus()) { if (playOnAudioFocus) { startPlayback(); } } }, 1000); // 1秒后重试 } - 
用户触发重试:
public void handleAudioFocusFailure() { // 显示提示给用户 showPlaybackErrorDialog("无法获取音频焦点,是否重试?", () -> { if (audioFocusHelper.requestAudioFocus()) { startPlayback(); } else { showToast("获取音频焦点失败"); } }); } 
九、最佳实践与注意事项
- 
始终释放音频焦点:当应用不再需要播放音频时,应及时释放音频焦点,以便其他应用可以使用。
 - 
处理各种焦点变化:正确处理 AUDIOFOCUS_GAIN、AUDIOFOCUS_LOSS、AUDIOFOCUS_LOSS_TRANSIENT 和 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 等各种情况。
 - 
使用 AndroidX 库:使用 AndroidX 中的 MediaSessionCompat 和其他兼容性类,确保在各个 Android 版本上的一致行为。
 - 
创建通知渠道:对于 Android 8.0 及以上版本,为前台服务创建适当的通知渠道。
 - 
考虑蓝牙设备:当应用通过蓝牙设备播放音频时,需要特别处理连接断开和重连的情况。
 - 
使用现代播放器:考虑使用 ExoPlayer 等现代媒体播放器,它们内置了对音频焦点的支持。
 
通过以上示例,我们可以看到音频焦点在 Android 应用中的重要性及其实现方式。合理使用音频焦点可以让应用提供更好的用户体验,与其他应用和谐共存。在实际开发中,建议根据具体场景选择合适的实现方式,并注意版本兼容性处理。
本文链接:Android 中音频焦点的使用场景及代码示例 - https://h89.cn/archives/476.html
版权声明:原创文章 遵循 CC 4.0 BY-SA 版权协议,转载请附上原文链接和本声明。
评论已关闭