Android使用MediaPlayer播放默认铃声时的IllegalStateException与权限问题
解决系统铃声循环播放的权限与MediaPlayer状态问题
看起来你遇到的问题主要来自两个方面:存储权限的动态申请和MediaPlayer的状态管理不当,另外我还会给你一个更简便的实现方案,不用自己折腾MediaPlayer。
一、先解决核心的权限问题
你已经在Manifest里加了READ_EXTERNAL_STORAGE权限,但从Android 6.0(API 23)开始,这类"危险权限"需要运行时动态申请,只在Manifest里声明是不够的。
因为Service本身没法直接弹出权限请求对话框,所以建议你在启动这个Service的Activity中先完成权限申请:
// 在启动Service的Activity中添加权限申请逻辑 private static final int REQUEST_READ_STORAGE = 1001; private void checkStoragePermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_READ_STORAGE); } else { // 权限已授予,启动Service startService(new Intent(this, MainService.class)); } } // 处理权限申请结果 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_READ_STORAGE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startService(new Intent(this, MainService.class)); } else { Toast.makeText(this, "需要读取存储权限才能播放系统铃声", Toast.LENGTH_SHORT).show(); } } }
二、修复MediaPlayer的状态异常问题
你遇到的IllegalStateException是因为MediaPlayer的状态不对——比如你可能重复使用了同一个MediaPlayer实例,而它已经处于Prepared或Started状态,再次调用prepareAsync()就会触发异常。
修改你的Service代码,确保每次启动都正确初始化MediaPlayer,并处理生命周期:
private MediaPlayer player; @Override public int onStartCommand(Intent intent, int flags, int startId) { // 先检查权限(保险起见,Service里再做一次判断) if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "权限未授予,无法播放铃声", Toast.LENGTH_SHORT).show(); stopSelf(); return START_NOT_STICKY; } // 释放旧的MediaPlayer实例,避免状态冲突 if (player != null) { player.release(); } player = new MediaPlayer(); // 设置音频属性(Android 8.0+推荐) player.setAudioAttributes(new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_RINGTONE) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build()); try { player.setDataSource(this, Settings.System.DEFAULT_RINGTONE_URI); } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "加载系统铃声失败", Toast.LENGTH_SHORT).show(); stopSelf(); return START_NOT_STICKY; } player.setOnPreparedListener(mp -> { mp.setLooping(true); mp.start(); }); // 添加错误监听,及时处理异常 player.setOnErrorListener((mp, what, extra) -> { Toast.makeText(this, "铃声播放出错", Toast.LENGTH_SHORT).show(); mp.release(); player = null; stopSelf(); return true; }); try { player.prepareAsync(); } catch (IllegalStateException e) { e.printStackTrace(); player.release(); player = null; stopSelf(); } Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show(); return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); // 释放MediaPlayer资源,避免内存泄漏 if (player != null) { player.stop(); player.release(); player = null; } }
三、更简便的实现方案:使用Ringtone类
其实Android已经提供了专门播放系统铃声的Ringtone类,它封装了MediaPlayer的所有逻辑,代码更简洁,还自带循环支持:
private Ringtone ringtone; @Override public int onStartCommand(Intent intent, int flags, int startId) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "请授予读取存储权限", Toast.LENGTH_SHORT).show(); stopSelf(); return START_NOT_STICKY; } // 获取系统默认铃声Uri Uri ringtoneUri = Settings.System.DEFAULT_RINGTONE_URI; // 创建Ringtone实例 ringtone = RingtoneManager.getRingtone(this, ringtoneUri); // 设置循环播放 ringtone.setLooping(true); // 开始播放 ringtone.play(); Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show(); return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); // 停止播放并释放资源 if (ringtone != null && ringtone.isPlaying()) { ringtone.stop(); } }
这个方案不需要你手动管理MediaPlayer的状态、prepare等步骤,直接调用play()和stop()就可以,推荐使用。
内容的提问来源于stack exchange,提问作者Keval
相关产品推荐
相关产品推荐

