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

Flutter中LateInitializationError求助:_controller未初始化

问题排查与修复方案

错误根源分析

1. 提前调用未初始化的控制器

在initState中直接执行_controller.initialize()和_controller.setVolume(1.0),但此时_controller仅通过late声明,未完成实例化赋值,直接触发LateInitializationError。

2. 单个控制器复用所有列表项

在ListView.builder的每个item中重复赋值全局_controller,会导致:

  • 每次列表项构建都会覆盖控制器,状态完全混乱
  • 多个视频共用一个控制器,播放/暂停操作互相干扰
  • 控制器生命周期无法与列表项绑定,引发内存泄漏

3. FutureBuilder的不当使用

直接在FutureBuilder的future参数中调用getVideoStatus(),会导致页面每次build时重新发起API请求,浪费资源且易引发异常。


修复步骤与代码示例

第一步:创建独立的列表项Widget(管理自身视频控制器)

每个视频项需持有专属的VideoPlayerController,单独处理初始化与销毁逻辑:

class VideoItemWidget extends StatefulWidget {
  final String videoLink;
  final VideoData serverData; // 替换为你的VideoModel对应数据类型
  final int progress;
  final Function(String) onDownloadTap;

  const VideoItemWidget({
    Key? key,
    required this.videoLink,
    required this.serverData,
    required this.progress,
    required this.onDownloadTap,
  }) : super(key: key);

  @override
  State<VideoItemWidget> createState() => _VideoItemWidgetState();
}

class _VideoItemWidgetState extends State<VideoItemWidget> {
  late VideoPlayerController _controller;
  late Future<void> _initializeFuture;
  Duration _position = Duration.zero;
  Duration _duration = Duration.zero;

  @override
  void initState() {
    super.initState();
    // 初始化当前项的视频控制器
    _controller = VideoPlayerController.network(widget.videoLink)
      ..addListener(_videoListener)
      ..setVolume(1.0);
    _initializeFuture = _controller.initialize().then((_) => setState(() {}));
  }

  void _videoListener() {
    if (mounted) {
      setState(() {
        _position = _controller.value.position;
        _duration = _controller.value.duration;
      });
    }
  }

  String _formatDuration(Duration duration) {
    String twoDigits(int n) => n.toString().padLeft(2, '0');
    final hours = twoDigits(duration.inHours);
    final minutes = twoDigits(duration.inMinutes.remainder(60));
    final seconds = twoDigits(duration.inSeconds.remainder(60));
    return [if (duration.inHours > 0) hours, minutes, seconds].join(':');
  }

  @override
  void dispose() {
    _controller.removeListener(_videoListener);
    _controller.dispose(); // 销毁当前项控制器,避免内存泄漏
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 5,
      color: Colors.blueGrey,
      shape: Border.all(color: Colors.black),
      child: Column(
        children: [
          Center(
            child: FutureBuilder<void>(
              future: _initializeFuture,
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.done) {
                  return AspectRatio(
                    aspectRatio: _controller.value.aspectRatio,
                    child: GestureDetector(
                      onTap: () {
                        setState(() {
                          _controller.value.isPlaying
                              ? _controller.pause()
                              : _controller.play();
                        });
                      },
                      child: Stack(
                        children: [
                          VideoPlayer(_controller),
                          if (!_controller.value.isPlaying)
                            Container(
                              alignment: Alignment.center,
                              color: Colors.black26,
                              child: const Icon(Icons.play_arrow, color: Colors.white, size: 80),
                            ),
                          Positioned(
                            bottom: 0,
                            left: 0,
                            right: 0,
                            child: VideoProgressIndicator(_controller, allowScrubbing: true),
                          ),
                          Positioned(
                            bottom: 0,
                            right: 0,
                            child: Padding(
                              padding: const EdgeInsets.all(8.0),
                              child: Row(
                                children: [
                                  Text(
                                    _formatDuration(_position),
                                    style: const TextStyle(fontSize: 11, color: Colors.white),
                                  ),
                                  const Text("/", style: TextStyle(fontSize: 12, color: Colors.white)),
                                  Text(
                                    _formatDuration(_duration),
                                    style: const TextStyle(fontSize: 11, color: Colors.white),
                                  ),
                                ],
                              ),
                            ),
                          ),
                          Positioned(
                            right: 0,
                            top: 0,
                            child: IconButton(
                              onPressed: () => widget.onDownloadTap(widget.videoLink),
                              icon: const Icon(Icons.download, color: Colors.white),
                            ),
                          ),
                        ],
                      ),
                    ),
                  );
                } else {
                  return const CircularProgressIndicator();
                }
              },
            ),
          ),
          const SizedBox(height: 5),
          Text(widget.serverData.title.toString()),
          Text("Download percentage   ${widget.progress}")
        ],
      ),
    );
  }
}

第二步:修改原页面核心代码

  1. 移除全局_controller和_initializeVideoPlayerFuture变量
  2. 将API请求Future存为成员变量,避免重复请求
  3. 重构ListView的itemBuilder,使用上述VideoItemWidget
class VideoStatusPage extends StatefulWidget {
  const VideoStatusPage({Key? key}) : super(key: key);

  @override
  State<VideoStatusPage> createState() => _VideoStatusPageState();
}

class _VideoStatusPageState extends State<VideoStatusPage> {
  int _progress = 0;
  ReceivePort receivePort = ReceivePort();
  late Future<VideoModel> _videoFuture; // 存储API请求Future
  int currentPage = 1;
  final RefreshController refreshController = RefreshController();
  VideoModel dummyData = VideoModel(); // 你的初始数据结构

  @override
  void initState() {
    super.initState();
    FlutterDownloader.registerCallback(downloadCallback);
    IsolateNameServer.registerPortWithName(
        receivePort.sendPort, "downloadingVideo");
    receivePort.listen((message) {
      setState(() {
        _progress = message;
      });
    });
    _videoFuture = getVideoStatus(); // 初始化API请求Future
  }

  Future<VideoModel> getVideoStatus() async {
    var url = "${masterApiUrl}getVideoStatus.php?page=$currentPage";
    var response = await http.get(Uri.parse(url));
    var jsondata = jsonDecode(response.body.toString());
    var _apiData = VideoModel.fromJson(jsondata);
    var newData = [...?dummyData.data, ...?_apiData.data];

    if (response.statusCode == 200) {
      if (_apiData.hasNextPage == 0) {
        refreshController.loadNoData();
      } else {
        refreshController.loadComplete();
      }
      final refids = newData.map((e) => e.id).toSet();
      newData.retainWhere((ids) => refids.remove(ids.id));
      dummyData.data = newData;
      return dummyData;
    } else {
      showToastMessage("some thing went wrong");
      return dummyData;
    }
  }

  static downloadCallback(id, status, progress) {
    SendPort? sendPort = IsolateNameServer.lookupPortByName("downloadingVideo");
    sendPort?.send(progress);
  }

  void _downloadFile(String videoLink) {
    // 你的下载逻辑
    print("download tap $videoLink");
  }

  @override
  void dispose() {
    receivePort.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(title: const Text("Video Status")),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: FutureBuilder<VideoModel>(
            future: _videoFuture,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return SmartRefresher(
                  controller: refreshController,
                  enablePullUp: true,
                  onLoading: () async {
                    if (snapshot.data!.hasNextPage == 0) {
                      refreshController.loadNoData();
                    } else {
                      setState(() {
                        currentPage++;
                      });
                      _videoFuture = getVideoStatus();
                      await Future.delayed(const Duration(milliseconds: 500));
                      refreshController.loadComplete();
                    }
                  },
                  child: ListView.builder(
                    itemCount: snapshot.data!.data!.length,
                    itemBuilder: (context, index) {
                      var serverData = snapshot.data!.data![index];
                      var globalLink = "https://Video_Status/";
                      var videoLink = globalLink + serverData.videoPath.toString();
                      return VideoItemWidget(
                        videoLink: videoLink,
                        serverData: serverData,
                        progress: _progress,
                        onDownloadTap: _downloadFile,
                      );
                    },
                  ),
                );
              } else if (snapshot.connectionState == ConnectionState.waiting) {
                return const Center(child: CircularProgressIndicator());
              } else if (snapshot.hasError) {
                return const Center(child: Text("Some thing went wrong !"));
              } else {
                return const Center(child: Text("Some thing went wrong"));
              }
            },
          ),
        ),
      ),
    );
  }
}

关键修复点总结

  • 每个列表项独立管理VideoPlayerController,避免全局复用导致的状态混乱与初始化错误
  • 移除initState中对未初始化控制器的调用
  • 将API请求Future存储为成员变量,避免每次build重复发起请求
  • 正确管理控制器生命周期,在列表项销毁时执行dispose,防止内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 09:17:03