Android MediaPlayer无法加载自定义通知音效(文件不存在错误)
问题分析与解决方案
核心问题拆解
你的代码存在三个致命问题,导致BroadcastReceiver中MediaPlayer无法加载自定义音频:
- MediaPlayer生命周期管理完全错误:初始化后立刻调用
mp1.release(),导致mp引用的是已释放的无效对象,根本无法播放。 - BroadcastReceiver生命周期不适合处理音频播放:
onReceive执行完毕后组件会被系统回收,prepareAsync异步准备还未完成就会被销毁,同步prepare又会阻塞主线程引发ANR。 - 文件扩展名获取逻辑错误:直接用
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
相关产品推荐
相关产品推荐

