Flutter后台录音报错:LateInitializationError,recorderController未初始化
Flutter后台录音LateInitializationError问题解决
问题
使用flutter_background_service实现后台录音功能,搭配audio_waveforms包时,运行持续抛出错误:
Unhandled Exception: LateInitializationError: Field 'recorderController' has not been initialized
错误原因分析
- 控制器未初始化:bplay类中声明了
late final RecorderController recorderController,但调用录音方法startOrStopRecording()前,从未执行initialiseControllers()完成控制器初始化。 - 实例重复创建:每次调用
bplay().getDir()和bplay().startOrStopRecording()都会生成新的bplay实例,导致初始化逻辑无法作用于当前使用的实例,控制器始终处于未初始化状态。 - 异步方法未等待:
getDir()是异步方法,当前代码直接同步调用后立即执行录音,会导致path2尚未赋值,同时控制器初始化也未完成。 - 控制器实例隔离:main.dart中全局声明的
recorderController与bplay类内部的控制器是独立实例,后台服务中初始化的控制器并未被bplay类使用。
修复方案
1. 复用bplay实例并确保初始化
在后台服务中创建单一bplay实例,先完成控制器初始化和目录获取,再执行录音操作。
2. 处理异步方法等待
确保异步方法执行完成后再调用后续逻辑,避免空值或未初始化问题。
3. 完善控制器初始化检查
在录音方法中增加控制器初始化状态检查,避免未初始化时调用方法。
修改后的代码示例
main.dart关键部分修改
@pragma('vm:entry-point') void onStart(ServiceInstance service) async { // 创建bplay实例并完成初始化 final bPlayInstance = bplay(); await bPlayInstance.initialiseControllers(); await bPlayInstance.getDir(); bool isRecording = false; DartPluginRegistrant.ensureInitialized(); final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); // ... 其他服务配置代码保持不变 ... Timer.periodic(const Duration(seconds: 10), (timer) async { if (service is AndroidServiceInstance) { if (await service.isForegroundService()) { // 复用已初始化的bplay实例执行录音 await bPlayInstance.startOrStopRecording(); flutterLocalNotificationsPlugin.show( 888, 'COOL SERVICE', 'Recording', NotificationDetails( android: AndroidNotificationDetails( 'my_foreground', 'SpeakBit SERVICE', icon: 'ic_bg_service_small', ongoing: true, ), ), ); } } // ... 设备信息上报代码保持不变 ... }); }
bplay类修改
class bplay { bplay(); var audiosuper; RecorderController? recorderController; // 改为可空类型,避免late未初始化问题 String? path2; Future<void> getDir() async { final directory = await getApplicationDocumentsDirectory(); path2 = "${directory.path}/test${DateTime.now().toIso8601String()}.m4a"; print('this is path 2:${path2}'); } Future<void> initialiseControllers() async { if (await Permission.microphone.request().isGranted) { recorderController = RecorderController() ..androidEncoder = AndroidEncoder.aac ..androidOutputFormat = AndroidOutputFormat.mpeg4 ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC ..sampleRate = 16000; } else { // 处理麦克风权限未获取的情况 print("麦克风权限未授权"); } } Future<void> startOrStopRecording() async { // 检查控制器是否初始化以及路径是否存在 if (recorderController == null || path2 == null) { print("控制器未初始化或路径为空"); return; } await recorderController!.record(path: path2); // 注意:原代码中cancel()会立即取消定时器,这里需要移除cancel()才能执行停止逻辑 Timer(const Duration(seconds: 10), () async { final path = await recorderController!.stop(false); if (path != null) { await audioconverter(path); } recorderController!.setRefresh(false); recorderController!.reset(); // 每次录音后重新生成新路径,避免覆盖 await getDir(); }); } Future<void> audioconverter(String audio) async { String output1 = '${audio}.wav'; String command = '-i ${audio} -af "afftdn=nf=-75:tn=1:nr=30" ${output1}'; final result = await FFmpegKit.execute(command); // 检查FFmpeg执行结果 final returnCode = await result.getReturnCode(); if (returnCode?.isValueSuccess() == true && output1.isNotEmpty) { audiosuper = output1; var now = DateTime.now(); var formatter = DateFormat('d MMM, yyyy'); var endtime = DateFormat("hh:mm").format(DateTime.now()); var formattedDate = formatter.format(now); audioList.add( { 'audio': audiosuper, 'date': formattedDate, 'time': endtime, 'super': audiosuper }, ); } else { print("音频转换失败"); } } }
额外注意事项
- 原代码中
Timer调用后立即执行cancel(),会导致录音无法自动停止,需移除该cancel()调用。 - 每次录音完成后重新调用
getDir()生成新路径,避免后续录音覆盖之前的文件。 - 增加权限检查和错误处理逻辑,提升代码稳定性。
内容的提问来源于stack exchange,提问作者wale kuye
相关产品推荐
相关产品推荐

