You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:26:37