AudioManager(音频管理器)
使用 AudioManager (音频管理器)
AudioManager
是 Android 提供的一个系统服务类,用于管理和控制设备的音频相关功能,如音量调节、音频模式切换(如静音、振动)、音频焦点管理以及音频路由(如扬声器、耳机)。结合 Android 动画合集的背景,AudioManager
可用于在动画触发时调整音效音量(如 SoundPool
或 MediaPlayer
的播放音量),或根据音频焦点变化暂停动画。本文将详细介绍 AudioManager
的功能、使用方法、权限要求及注意事项,重点展示如何管理音量和音频焦点,并结合动画实现交互效果。
AudioManager 的作用与原理
- 作用:
AudioManager
提供对设备音频系统的控制,包括音量调整、音频模式(如响铃、静音)、音频焦点管理和音频输出设备选择。它是与音频相关的核心管理类,适用于各种音频场景(如音乐、通知、电话)。 - 原理:
AudioManager
通过 Android 的音频框架与底层硬件通信,管理音频流(如音乐、铃声、通话音量)和音频焦点(协调多个应用的音频播放)。它支持多种音频流类型(如STREAM_MUSIC
、STREAM_ALARM
),并提供 API 调整音量或监听焦点变化。 - 应用场景:
- 调整音效或背景音乐的音量(如动画触发时的
SoundPool
音量)。 - 在来电或焦点丢失时暂停动画和音频(如与
TelephonyManager
结合)。 - 切换音频模式(如静音模式暂停通知音效)。
- 检测耳机插入以调整动画行为。
- 实现音频焦点管理,确保多个应用的音频协调播放。
权限要求
AudioManager
的大多数功能无需权限,但某些操作(如修改系统设置或监听电话状态)需要声明权限:
<!-- 修改音频设置(如静音) -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- 可选:与电话状态结合 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
- 动态权限(API 23+):
MODIFY_AUDIO_SETTINGS
:修改音频设置(如音量、静音)。READ_PHONE_STATE
(可选):监听电话状态以协调音频。- 使用
ActivityCompat.requestPermissions
请求权限。
获取 AudioManager
通过 Context
获取 AudioManager
实例:
import android.content.Context;
import android.media.AudioManager;
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
常用功能与方法
以下是 AudioManager
的核心功能和方法:
方法/功能 | 描述 | 权限要求 | 用法示例 |
---|---|---|---|
getStreamVolume(int streamType) | 获取指定音频流的当前音量。 | 无 | int volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); |
setStreamVolume(int streamType, int index, int flags) | 设置指定音频流的音量。 | MODIFY_AUDIO_SETTINGS | audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 10, 0); |
getStreamMaxVolume(int streamType) | 获取指定音频流的最大音量。 | 无 | int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); |
setMode(int mode) | 设置音频模式(如正常、静音、振铃)。 | MODIFY_AUDIO_SETTINGS | audioManager.setMode(AudioManager.MODE_NORMAL); |
requestAudioFocus(AudioFocusRequest) | 请求音频焦点(API 26+)。 | 无 | audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).build()); |
abandonAudioFocus(OnAudioFocusChangeListener) | 放弃音频焦点。 | 无 | audioManager.abandonAudioFocus(listener); |
isMusicActive() | 检查是否有音乐正在播放。 | 无 | boolean active = audioManager.isMusicActive(); |
setSpeakerphoneOn(boolean) | 开启/关闭扬声器。 | MODIFY_AUDIO_SETTINGS | audioManager.setSpeakerphoneOn(true); |
isWiredHeadsetOn() | 检查是否插入有线耳机(API 23 弃用,建议用 AudioDeviceInfo)。 | 无 | boolean headset = audioManager.isWiredHeadsetOn(); |
- 音频流类型(
streamType
): AudioManager.STREAM_MUSIC
:媒体音量(如音乐、音效)。AudioManager.STREAM_RING
:铃声音量。AudioManager.STREAM_ALARM
:闹钟音量。AudioManager.STREAM_NOTIFICATION
:通知音量。AudioManager.STREAM_VOICE_CALL
:通话音量。- 音频模式(
setMode
): MODE_NORMAL
:正常模式。MODE_RINGTONE
:响铃模式。MODE_IN_CALL
:通话模式。MODE_IN_COMMUNICATION
:通信模式。- 音频焦点类型(API 26+):
AUDIOFOCUS_GAIN
:长期占用焦点(如音乐播放)。AUDIOFOCUS_GAIN_TRANSIENT
:临时占用焦点(如通知音)。AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
:允许其他应用降低音量继续播放。
完整示例(音量控制 + 音频焦点 + 动画)
以下是一个结合 AudioManager
和 SoundPool
(前文介绍)的示例,展示音量调节、音频焦点管理和按钮动画。
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.Toast;
import android.animation.ObjectAnimator;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class AudioManagerActivity extends AppCompatActivity {
private static final int REQUEST_AUDIO_PERMISSION = 100;
private AudioManager audioManager;
private SoundPool soundPool;
private int soundId;
private Button playButton;
private SeekBar volumeSeekBar;
private ObjectAnimator pulseAnimator;
private AudioFocusRequest focusRequest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_manager);
playButton = findViewById(R.id.play_button);
volumeSeekBar = findViewById(R.id.volume_seekbar);
// 初始化 AudioManager
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// 初始化 SoundPool
soundPool = new SoundPool.Builder()
.setMaxStreams(1)
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build())
.build();
soundId = soundPool.load(this, R.raw.click_sound, 1);
// 初始化动画
pulseAnimator = ObjectAnimator.ofFloat(playButton, "scaleX", 1f, 1.2f, 1f);
pulseAnimator.setDuration(500);
pulseAnimator.setRepeatCount(ObjectAnimator.INFINITE);
// 设置音量 SeekBar
int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
volumeSeekBar.setMax(maxVolume);
volumeSeekBar.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
volumeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, AudioManager.FLAG_SHOW_UI);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
// 请求音频焦点
focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build())
.setOnAudioFocusChangeListener(focusChange -> {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
soundPool.stop(soundId);
pulseAnimator.cancel();
Toast.makeText(AudioManagerActivity.this, "Audio focus lost", Toast.LENGTH_SHORT).show();
break;
case AudioManager.AUDIOFOCUS_GAIN:
// Resume playback if needed
break;
}
})
.build();
// 播放按钮
playButton.setOnClickListener(v -> {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.MODIFY_AUDIO_SETTINGS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.MODIFY_AUDIO_SETTINGS}, REQUEST_AUDIO_PERMISSION);
} else {
playSound();
}
});
}
private void playSound() {
int result = audioManager.requestAudioFocus(focusRequest);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
soundPool.play(soundId, 1.0f, 1.0f, 1, 0, 1.0f);
pulseAnimator.start();
} else {
Toast.makeText(this, "Failed to gain audio focus", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_AUDIO_PERMISSION && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
playSound();
} else {
Toast.makeText(this, "Audio permission denied", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onPause() {
super.onPause();
soundPool.autoPause();
pulseAnimator.cancel();
audioManager.abandonAudioFocusRequest(focusRequest);
}
@Override
protected void onDestroy() {
super.onDestroy();
soundPool.release();
soundPool = null;
pulseAnimator.cancel();
}
}
- 布局 XML(res/layout/activity_audio_manager.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<SeekBar
android:id="@+id/volume_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/play_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play Sound" />
</LinearLayout>
- AndroidManifest.xml(添加权限):
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
- 说明:
- 权限:动态请求
MODIFY_AUDIO_SETTINGS
权限。 - 音量控制:使用
SeekBar
调节STREAM_MUSIC
音量,实时更新。 - 音频焦点:请求
AUDIOFOCUS_GAIN
焦点,丢失焦点时暂停音效和动画。 - 动画:播放音效时按钮脉冲动画(
scaleX
),焦点丢失或暂停时取消。 - 生命周期:
onPause
暂停音效和动画,onDestroy
释放SoundPool
和动画。
音频焦点管理
音频焦点确保多个应用的音频协调播放。API 26+ 使用 AudioFocusRequest
,旧版本使用 requestAudioFocus(OnAudioFocusChangeListener, int)
。
- 请求焦点(API 26+):
AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setOnAudioFocusChangeListener(focusChangeListener)
.build();
int result = audioManager.requestAudioFocus(focusRequest);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// Play audio
}
- 旧版焦点请求(API < 26):
AudioManager.OnAudioFocusChangeListener listener = focusChange -> {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// Stop playback
}
};
audioManager.requestAudioFocus(listener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
- 焦点变化:
AUDIOFOCUS_LOSS
:永久丢失焦点(如其他应用播放音乐)。AUDIOFOCUS_LOSS_TRANSIENT
:临时丢失焦点(如通知音)。AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
:可降低音量继续播放。AUDIOFOCUS_GAIN
:获得焦点。
结合 TelephonyManager
结合 TelephonyManager
(前文介绍)暂停音效和动画:
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
PhoneStateListener listener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String phoneNumber) {
if (state == TelephonyManager.CALL_STATE_RINGING || state == TelephonyManager.CALL_STATE_OFFHOOK) {
soundPool.stop(soundId);
pulseAnimator.cancel();
audioManager.abandonAudioFocusRequest(focusRequest);
}
}
};
telephonyManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
优缺点
- 优点:
- 提供全面的音频管理功能(音量、模式、焦点)。
- 与
SoundPool
、MediaPlayer
等无缝集成。 - 支持音频焦点协调多应用播放。
- 缺点:
- 部分功能需权限(如
MODIFY_AUDIO_SETTINGS
)。 - 设备差异可能影响行为(如耳机检测)。
- API 26 前焦点管理较复杂。
- 替代方案:
- MediaSession:现代媒体控制(API 21+)。
- AudioTrack:低级音频播放。
- Jetpack Media3:高级音频/视频管理。
- VolumeProvider:自定义音量控制。
注意事项
- 权限:API 23+ 需动态请求
MODIFY_AUDIO_SETTINGS
。 - 音频焦点:始终请求和放弃焦点,避免干扰其他应用。
- 生命周期:在
onPause
或onDestroy
暂停音频、取消动画、放弃焦点。 - 设备兼容性:检查设备支持的音频流和模式(如无扬声器设备)。
- 调试:通过 Log 检查音量、焦点状态,验证音频行为。
扩展:Jetpack Compose 中的实现
在 Jetpack Compose 中,AudioManager
可结合 Composable 实现音量控制和动画:
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.media.SoundPool
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Slider
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.infiniteRepeatable
@Composable
fun AudioManagerScreen() {
val context = LocalContext.current
val audioManager = remember { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager }
val soundPool = remember {
SoundPool.Builder()
.setMaxStreams(1)
.setAudioAttributes(AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build())
.build()
}
val soundId = remember { soundPool.load(context, R.raw.click_sound, 1) }
var volume by remember { mutableStateOf(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat()) }
var isPlaying by remember { mutableStateOf(false) }
val scale by animateFloatAsState(
targetValue = if (isPlaying) 1.2f else 1f,
animationSpec = infiniteRepeatable(tween(500))
)
val focusRequest = remember {
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build())
.setOnAudioFocusChangeListener { focusChange ->
if (focusChange == AudioManager.AUDIOFOCUS_LOSS || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
soundPool.stop(soundId)
isPlaying = false
}
}
.build()
}
DisposableEffect(Unit) {
onDispose {
soundPool.release()
audioManager.abandonAudioFocusRequest(focusRequest)
}
}
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Volume: ${volume.toInt()}")
Slider(
value = volume,
onValueChange = {
volume = it
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume.toInt(), AudioManager.FLAG_SHOW_UI)
},
valueRange = 0f..audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat(),
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(16.dp))
Button(
onClick = {
val result = audioManager.requestAudioFocus(focusRequest)
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
soundPool.play(soundId, 1.0f, 1.0f, 1, 0, 1.0f)
isPlaying = true
}
},
modifier = Modifier.scale(scale)
) {
Text("Play Sound")
}
}
}
- 说明:
- 使用
remember
缓存AudioManager
和SoundPool
。 Slider
控制STREAM_MUSIC
音量。animateFloatAsState
实现按钮脉冲动画。AudioFocusRequest
管理音频焦点,丢失焦点时停止播放。DisposableEffect
确保资源释放。
示例:静音模式切换
切换静音模式并暂停动画:
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
pulseAnimator.cancel();
Toast.makeText(this, "Silent mode enabled", Toast.LENGTH_SHORT).show();
If you need more advanced features (e.g., real-time volume visualization, earphone detection, or integration with Canvas for audio animations) or want to explore other parts of the Android animation collection (e.g., transition animations, Lottie), please let me know!