Flutter短视频Reels项目中基于BetterPlayer的预加载与OOM内存溢出问题排查求助
Flutter短视频Reels项目中基于BetterPlayer的预加载与OOM内存溢出问题排查求助
问题背景
我正在开发一款类似Instagram Reels或TikTok的Flutter短视频应用,用better_player_plus处理视频播放。为了提升滚动播放的流畅度,我做了预加载下5个、上2个视频的逻辑,但现在遇到了严重问题:滚动视频时APP直接崩溃,抛出OutOfMemoryError:
Throwing OutOfMemoryError "Failed to allocate a 40 byte allocation with 786656 free bytes and 768KB until OOM, target footprint 603979776, growth limit 603979776; giving up on allocation because <1% of heap free after GC." (VmSize 26710928 kB)
而且切换到下一个视频时还会有轻微的加载延迟。
已尝试的方案
我参考AI建议实现了LRU(最近最少使用)缓存类,用来管理BetterPlayer控制器,希望通过销毁闲置控制器来控制内存,但问题依然存在,具体实现如下:
1. 实现LRU缓存管理类
这个类的核心是达到容量上限时,自动销毁最久未使用的BetterPlayer控制器:
import 'dart:collection'; import 'package:better_player_plus/better_player_plus.dart'; class LRUCache<K, V> { final int capacity; final LinkedHashMap<K, V> _cache; LRUCache(this.capacity) : _cache = LinkedHashMap(); V? get(K key) { final value = _cache.remove(key); if (value != null) { _cache[key] = value; // 移到末尾标记为最近使用 } return value; } void put(K key, V value) { if (_cache.containsKey(key)) { _cache.remove(key); } else if (_cache.length >= capacity) { final lruKey = _cache.keys.first; final lruValue = _cache.remove(lruKey); if (lruValue is BetterPlayerController) { lruValue.pause(); lruValue.removeEventsListener((event) {}); lruValue.dispose(forceDispose: true); } } _cache[key] = value; } void remove(K key) { final value = _cache.remove(key); if (value is BetterPlayerController) { value.pause(); value.removeEventsListener((event) {}); value.dispose(forceDispose: true); } } void clear() { _cache.forEach((_, value) { if (value is BetterPlayerController) { value.pause(); value.removeEventsListener((event) {}); value.dispose(forceDispose: true); } }); _cache.clear(); } }
2. 整合LRU缓存到视频播放逻辑
在视频Widget的State中,用LRU缓存管理控制器,同时实现了下一个、下下个视频的预加载:
class _VideoByteReelWidgetState extends State<VideoByteReelWidget> { BetterPlayerController? videoController; static final LRUCache<int, BetterPlayerController> _controllerCache = LRUCache(5); // 缓存上限设为5个控制器 Future<void> initVideoPlayerController(String? videoUrl, {double? aspectRatio, Widget? placeHolderWidget}) async { if (videoUrl == null || videoController?.isVideoInitialized() == true) { return; } // 优先从缓存获取控制器 videoController = _controllerCache.get(byte.id); if (videoController != null) { onVideoInitialized(); if (mounted) setState(() {}); return; } // 缓存无对应控制器则初始化新实例 videoController = BetterPlayerController( _initBetterPlayerConfiguration(aspectRatio: aspectRatio, placeHolderWidget: placeHolderWidget), betterPlayerDataSource: _initBetterPlayerDataSource(videoUrl), betterPlayerPlaylistConfiguration: const BetterPlayerPlaylistConfiguration(), ); // 将新控制器存入缓存 _controllerCache.put(byte.id, videoController!); if (mounted) { setState(() {}); } videoController!.setControlsAlwaysVisible(true); videoController!.setControlsEnabled(true); videoController!.setVolume(1.0); videoController?.addEventsListener(_onPlayerEvent); if (!(await WakelockPlus.enabled)) { WakelockPlus.enable(); } // 预加载下一个视频 if (widget.nextByte != null && widget.nextByte!.cdnUrl != null && nextByteIdKey != widget.nextByte?.id) { nextByteIdKey = widget.nextByte!.id; nextByteVideoController = _controllerCache.get(nextByteIdKey!); if (nextByteVideoController == null) { final nextDataSource = _initBetterPlayerDataSource(widget.nextByte!.cdnUrl!); nextByteVideoController = BetterPlayerController( _initBetterPlayerConfiguration( aspectRatio: computeByteAspectRation(widget.nextByte!.videoAspectRatio!), placeHolderWidget: _buildVideoImagePlaceHolder(widget.nextByte!.thumbCdnUrl), ), betterPlayerDataSource: nextDataSource, betterPlayerPlaylistConfiguration: const BetterPlayerPlaylistConfiguration(), ); _controllerCache.put(nextByteIdKey!, nextByteVideoController!); await nextByteVideoController?.preCache(nextDataSource); } } // 预加载下下个视频(注:原文此处代码存在截断) if (widget.nextNextByte != null && widget.nextNextByte!.cdnUrl != null && nextNextByteIdKey != widget.nextNextByte?.id) { nextNextByteIdKey = widget.nextNextByte!.id; nextNextByteVideoController = _controllerCache.get(nextNextByteIdKey!); if (nextNextByteVideoController == null) { final nextNextDataSource = _initBetterPlayerDataSource(widget.nextNextByte!.cdnUrl!); nextNextByteVideoController = BetterPlayerController( _initBetterPlayerConfiguration( aspectRatio: computeByteAspectRation(widget.nextNextByte!.videoAspectRatio!), placeHolderWidget: _buildVideoImagePlaceHolder(widget.nextNextByte!.thumbCdnUrl), ), betterPlayerDataSource: nextNextDataSource, betterPlayerPlaylistConfiguration: const BetterPlayerPlaylistConfiguration(), ); _controllerCache.put(nextNextByteIdKey!, nextNextByteVideoController!); await nextNextByteVideoController?.preCache(nextNextDataSource); } } } // 以下省略_onPlayerEvent、onVideoInitialized等辅助方法实现 }
想请教的问题
目前虽然用了LRU缓存,但OOM崩溃和切换延迟的问题还是没解决,想问问各位大佬:
- 我的LRU缓存实现有没有漏洞?比如销毁控制器的逻辑是否彻底,会不会有内存泄漏?
- 预加载的逻辑是否合理?比如预加载的数量、时机有没有优化空间?
- 有没有更成熟的BetterPlayer控制器管理方案,能有效避免内存溢出?
- 切换视频的延迟该从哪些方向优化?
内容来源于stack exchange




