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

Android MediaPlayer无法加载自定义通知音效(文件不存在错误)

问题分析与解决方案

核心问题拆解

你的代码存在三个致命问题,导致BroadcastReceiver中MediaPlayer无法加载自定义音频:

  1. MediaPlayer生命周期管理完全错误:初始化后立刻调用mp1.release(),导致mp引用的是已释放的无效对象,根本无法播放。
  2. BroadcastReceiver生命周期不适合处理音频播放:onReceive执行完毕后组件会被系统回收,prepareAsync异步准备还未完成就会被销毁,同步prepare又会阻塞主线程引发ANR。
  3. 文件扩展名获取逻辑错误:直接用Uri.getPath()创建File只对file://类型Uri有效,content://类型的Uri路径并非真实文件路径,导致扩展名识别错误,甚至复制出损坏的音频文件。

分步解决方案

1. 修复文件复制时的扩展名获取逻辑

替换原文件选择器中获取扩展名的代码,改用ContentResolver获取真实文件名或MIME类型:

// 替换原扩展名获取代码
String extension = null;
// 方式1:通过MIME类型推导扩展名
String mimeType = getContentResolver().getType(uri);
if (mimeType != null) {
    extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
}
// 方式2:通过Cursor获取真实文件名
if (extension == null) {
    Cursor cursor = getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
    if (cursor != null && cursor.moveToFirst()) {
        String displayName = cursor.getString(0);
        int dotIndex = displayName.lastIndexOf(".");
        if (dotIndex != -1) {
            extension = displayName.substring(dotIndex).trim();
        }
        cursor.close();
    }
}
// 后续判断逻辑不变
if (extension != null && supportedFormats.contains(extension) && stream != null) {
    // ... 复制文件代码
}

2. 用IntentService处理音频播放

BroadcastReceiver不适合持有MediaPlayer,改用IntentService(后台服务,自带子线程)处理播放逻辑:

public class NotificationSoundService extends IntentService {
    private static final String ACTION_PLAY = "com.wotrox.qvkrfire.action.PLAY_NOTIF";

    public NotificationSoundService() {
        super("NotificationSoundService");
    }

    // 对外提供启动播放的方法
    public static void startPlay(Context context) {
        Intent intent = new Intent(context, NotificationSoundService.class);
        intent.setAction(ACTION_PLAY);
        ContextCompat.startForegroundService(context, intent);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (ACTION_PLAY.equals(intent.getAction())) {
            playSound();
        }
    }

    private void playSound() {
        SharedPreferences setts = PreferenceManager.getDefaultSharedPreferences(this);
        MediaPlayer mp = null;
        try {
            if (setts.getInt("notif_sound", 0) == -1 && setts.getString("custom_notif_sound", null) != null) {
                String filename = setts.getString("custom_notif_sound", null);
                File audioFile = new File(getFilesDir(), filename);
                if (audioFile.exists()) {
                    mp = new MediaPlayer();
                    // 设置通知音频属性,符合系统规范
                    mp.setAudioAttributes(new AudioAttributes.Builder()
                            .setUsage(AudioAttributes.USAGE_NOTIFICATION)
                            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                            .build());
                    mp.setDataSource(audioFile.getAbsolutePath());
                    mp.prepare(); // 子线程中可同步调用,无需异步
                    mp.start();
                    // 播放完成后自动释放资源
                    mp.setOnCompletionListener(MediaPlayer::release);
                    return;
                }
            }
            // 自定义音效加载失败时,播放默认音效
            int resId = setts.getInt("notif_sound", 0) == 1 ? R.raw.alerttwo : R.raw.alertone;
            mp = MediaPlayer.create(this, resId);
            mp.start();
            mp.setOnCompletionListener(MediaPlayer::release);
        } catch (Exception e) {
            Log.e("qvkrimp", "播放失败", e);
            if (mp != null) mp.release();
            // 最终 fallback 到默认音效
            MediaPlayer fallback = MediaPlayer.create(this, R.raw.alertone);
            fallback.start();
            fallback.setOnCompletionListener(MediaPlayer::release);
        }
    }
}

3. 修改BroadcastReceiver逻辑

在BroadcastReceiver的onReceive中启动服务即可,不再直接操作MediaPlayer:

@Override
public void onReceive(Context context, Intent intent) {
    // 其他逻辑...
    NotificationSoundService.startPlay(context);
}

4. 清单文件注册Service

在AndroidManifest.xml中添加Service声明:

<service
    android:name=".NotificationSoundService"
    android:exported="false"
    android:foregroundServiceType="mediaPlayback"/>

关键说明

  • 为什么Activity中能运行?:Activity生命周期较长,即使提前释放MediaPlayer,短时间内可能还未被系统回收,侥幸能播放,但这是错误写法,同样存在崩溃风险。
  • 为什么不用prepareAsync?:在后台服务的子线程中,同步prepare更可靠,无需监听准备状态,代码逻辑更简洁。
  • 音频属性的重要性:设置USAGE_NOTIFICATION可确保音效遵循系统通知音量规则,不会被媒体音量影响。

内容的提问来源于stack exchange,提问作者WoTrox

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 22:32:12