Flutter应用使用just_audio_background在OnePlus 8T播放时崩溃
问题概述
使用just_audio_background实现后台音频播放的Flutter应用,在OnePlus 8T设备上关闭应用设置中的「Audio Playback」静默通知分类后,启动音频播放时触发崩溃,报错如下:
D/OplusScrollToTopManager(31518): com.example.yessey/com.ryanheise.audioservice.AudioServiceActivity,This DecorView@6dbafe8[AudioServiceActivity] change focus to true
D/AndroidRuntime(31518): Shutting down VM
E/AndroidRuntime(31518): FATAL EXCEPTION: main
E/AndroidRuntime(31518): Process: com.example.yessey, PID: 31518
E/AndroidRuntime(31518): android.app.RemoteServiceException$BadForegroundServiceNotificationException: Bad notification(tag=null, id=1124) posted from package com.example.yessey, crashing app(uid=11029, pid=31518): Couldn't inflate contentViewsjava.lang.IndexOutOfBoundsException: Index: 3, Size: 3
应用在模拟器及开启该通知分类时运行正常,卸载重装、移除专辑图片等操作均无法解决问题。
问题原因
OnePlus定制ROM对通知渠道的权限管控存在特殊逻辑:当用户禁用了对应通知渠道(或关闭静默通知分类),just_audio_background尝试通过该渠道创建前台服务通知时,系统会拒绝并抛出未被插件捕获的异常,直接导致应用崩溃。
解决方案
1. 显式设置通知渠道重要性
在初始化just_audio_background时,添加渠道重要性配置,确保渠道创建时的优先级,降低被系统标记为易禁用类型的概率:
void main() async { WidgetsFlutterBinding.ensureInitialized(); await Wakelock.enable(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); try { await JustAudioBackground.init( androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio', androidNotificationChannelName: 'Audio playback', androidNotificationOngoing: true, // 添加渠道重要性设置 androidNotificationChannelImportance: AndroidNotificationImportance.high, ); } catch (e) { print("JustAudioBackground初始化错误: $e"); } }
2. 捕获播放阶段异常
在启动播放的方法中添加try-catch块,捕获平台异常并处理,避免应用直接崩溃:
Future<void> initializePlayer() async { try { final audioSource = AudioSource.uri( Uri.parse('https://example/mute.mp3'), tag: MediaItem( id: '1', album: "per salvare il replay!", title: "Cambia volume", artUri: Uri.parse('https://example.png'), ), ); await _player.setAudioSource(audioSource); await _player.setLoopMode(LoopMode.one); await _player.play(); } catch (e) { print("播放启动失败: $e"); // 可在此处添加用户提示,例如弹出AlertDialog告知需开启通知权限 } }
3. 提前检查通知渠道状态(需Platform Channel实现)
通过原生Android API检查对应通知渠道是否可用,在播放前引导用户开启权限:
Flutter端代码
import 'package:flutter/services.dart'; class NotificationChannelChecker { static const MethodChannel _channel = MethodChannel('notification_channel_checker'); static Future<bool> isChannelEnabled(String channelId) async { try { return await _channel.invokeMethod('isChannelEnabled', {'channelId': channelId}); } on PlatformException catch (e) { print("检查渠道状态失败: ${e.message}"); return false; } } }
Android端代码(MainActivity中)
import android.app.NotificationManager import android.content.Context import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { private val CHANNEL = "notification_channel_checker" override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "isChannelEnabled") { val channelId = call.argument<String>("channelId") val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val channel = notificationManager.getNotificationChannel(channelId) val isEnabled = channel != null && channel.importance != NotificationManager.IMPORTANCE_NONE result.success(isEnabled) } else { result.notImplemented() } } } }
使用时,在initializePlayer前调用:
bool isChannelEnabled = await NotificationChannelChecker.isChannelEnabled('com.ryanheise.bg_demo.channel.audio'); if (!isChannelEnabled) { // 引导用户前往应用设置开启通知渠道 } else { // 启动播放 }
4. 请求通知权限(Android 13+)
使用permission_handler插件请求POST_NOTIFICATIONS权限,确保应用拥有创建通知的基础权限:
import 'package:permission_handler/permission_handler.dart'; // 在应用启动或播放前调用 await Permission.notification.request();
内容的提问来源于stack exchange,提问作者speedMark

