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

Flutter后台录音报错:LateInitializationError,recorderController未初始化

Flutter后台录音LateInitializationError问题解决

问题

使用flutter_background_service实现后台录音功能,搭配audio_waveforms包时,运行持续抛出错误:

Unhandled Exception: LateInitializationError: Field 'recorderController' has not been initialized

错误原因分析

  1. 控制器未初始化:bplay类中声明了late final RecorderController recorderController,但调用录音方法startOrStopRecording()前,从未执行initialiseControllers()完成控制器初始化。
  2. 实例重复创建:每次调用bplay().getDir()和bplay().startOrStopRecording()都会生成新的bplay实例,导致初始化逻辑无法作用于当前使用的实例,控制器始终处于未初始化状态。
  3. 异步方法未等待:getDir()是异步方法,当前代码直接同步调用后立即执行录音,会导致path2尚未赋值,同时控制器初始化也未完成。
  4. 控制器实例隔离: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 00:55:10