Android应用播放raw目录5首音乐的最优方案及异常解决
Android Raw资源音乐播放:MediaPlayer vs 默认播放器,问题排查与最优方案
首先咱们先明确最优方案:对于你这种需要在App内用按钮分别控制5首raw资源音乐播放的场景,直接使用MediaPlayer绝对是最优选择,而不是调用系统默认播放器。
为什么这么说?
- 调用系统默认播放器会跳转到第三方音乐应用,你的App完全失去对播放流程的控制,用户体验会被打断;
- MediaPlayer允许你在当前Activity内完全掌控播放、暂停、切换的状态,完美匹配你要的按钮控制逻辑,还能自定义播放交互。
你的代码问题排查
初始代码崩溃原因
- 缺少按钮点击监听绑定:你在
onCreate里只通过findViewById获取了按钮实例,但没有调用aBTN.setOnClickListener(this);这类代码。如果你的布局里给按钮设置了android:onClick属性,系统找不到对应的处理方法,就会抛出IllegalStateException: Could not execute method for android:onClick。 - 变量命名混乱:比如MediaPlayer
e对应R.raw.f,ButtonfBTN绑定的是R.id.e,这种混乱很容易导致后续空指针异常。 - 未判空就调用状态方法:直接调用
b.isPlaying()这类方法,如果MediaPlayer初始化失败(比如资源不存在),会触发NullPointerException。
修改后代码的问题
- MediaPlayer状态错误操作:刚通过
MediaPlayer.create创建实例就调用stop(),此时MediaPlayer处于prepared状态,只有started状态才能调用stop(),会触发IllegalStateException。 - 重复释放资源:
onStop方法里重复调用currentMedia.stop()和release(),第一次release()后currentMedia已经为null,第二次调用会触发NullPointerException。 - 播放逻辑不完善:没有正确处理“同一首歌暂停后继续播放”的场景,逻辑分支存在错误。
修正后的完整代码
下面是优化后的代码,解决了上述所有问题,并且复用单个MediaPlayer节省内存:
public class MusicActivity extends AppCompatActivity implements View.OnClickListener { private MediaPlayer currentMediaPlayer; private Button btnA, btnB, btnC, btnD, btnE; // 记录当前正在播放的资源ID,0表示未播放 private int currentPlayingResId = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_music); // 绑定按钮并设置点击监听 btnA = findViewById(R.id.a); btnB = findViewById(R.id.b); btnC = findViewById(R.id.c); btnD = findViewById(R.id.d); btnE = findViewById(R.id.e); btnA.setOnClickListener(this); btnB.setOnClickListener(this); btnC.setOnClickListener(this); btnD.setOnClickListener(this); btnE.setOnClickListener(this); } @Override public void onClick(View view) { int targetResId = 0; switch (view.getId()) { case R.id.a: targetResId = R.raw.a; break; case R.id.b: targetResId = R.raw.b; break; case R.id.c: targetResId = R.raw.c; break; case R.id.d: targetResId = R.raw.d; break; case R.id.e: targetResId = R.raw.f; // 这里注意你的资源对应关系,根据实际情况调整 break; } if (targetResId != 0) { handleMusicPlay(targetResId); } } private void handleMusicPlay(int targetResId) { // 点击的是当前正在播放的音乐 if (currentPlayingResId == targetResId) { if (currentMediaPlayer != null && currentMediaPlayer.isPlaying()) { currentMediaPlayer.pause(); } else if (currentMediaPlayer != null) { currentMediaPlayer.start(); } return; } // 切换到新的音乐,先释放之前的资源 releaseCurrentMediaPlayer(); // 创建新的MediaPlayer并播放 currentMediaPlayer = MediaPlayer.create(this, targetResId); if (currentMediaPlayer != null) { currentMediaPlayer.start(); currentPlayingResId = targetResId; // 可选:设置播放完成监听,播放完后重置状态 currentMediaPlayer.setOnCompletionListener(mp -> { releaseCurrentMediaPlayer(); currentPlayingResId = 0; }); } } // 统一释放MediaPlayer资源 private void releaseCurrentMediaPlayer() { if (currentMediaPlayer != null) { if (currentMediaPlayer.isPlaying()) { currentMediaPlayer.stop(); } currentMediaPlayer.release(); currentMediaPlayer = null; } } @Override protected void onStop() { super.onStop(); // 页面停止时释放资源 releaseCurrentMediaPlayer(); currentPlayingResId = 0; } @Override protected void onDestroy() { super.onDestroy(); releaseCurrentMediaPlayer(); } }
代码优化点说明
- 复用单个MediaPlayer:避免创建多个实例占用内存,统一管理播放状态。
- 完善的状态判断:所有操作前都判空,避免空指针;严格遵循MediaPlayer的状态流程。
- 统一资源释放:封装
releaseCurrentMediaPlayer方法,在页面停止、销毁、切换音乐时都调用,避免内存泄漏。 - 清晰的变量命名:避免之前的命名混乱问题,代码可读性更强。
- 播放完成监听:可选添加,播放完成后自动重置状态,避免无效引用。
内容的提问来源于stack exchange,提问作者user4568864
相关产品推荐
相关产品推荐

