Flutter应用ExoPlayer触发OutOfMemoryError崩溃问题求助
问题描述
应用运行一段时间后出现内存溢出崩溃,崩溃日志如下:
E/AndroidRuntime(15295): FATAL EXCEPTION: ExoPlayer:Loader:ProgressiveMediaPeriod E/AndroidRuntime(15295): Process: com.app.dubfeed, PID: 15295 E/AndroidRuntime(15295): java.lang.OutOfMemoryError: Failed to allocate a 3944 byte allocation with 104656 free bytes and 102KB until OOM, target footprint 268435456, growth limit 268435456; giving up on allocation because <1% of heap free after GC. E/AndroidRuntime(15295): at java.util.Arrays.copyOf(Arrays.java:3578) E/AndroidRuntime(15295): at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:177) E/AndroidRuntime(15295): at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:638) E/AndroidRuntime(15295): at java.lang.StringBuilder.append(StringBuilder.java:226) E/AndroidRuntime(15295): at java.lang.String.replace(String.java:2843) E/AndroidRuntime(15295): at com.google.android.exoplayer2.util.Log.appendThrowableString(Log.java:295) E/AndroidRuntime(15295): at com.google.android.exoplayer2.util.Log.e(Log.java:254) E/AndroidRuntime(15295): at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:440) E/AndroidRuntime(15295): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) E/AndroidRuntime(15295): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) E/AndroidRuntime(15295): at java.lang.Thread.run(Thread.java:1012)
Manifest已配置android:hardwareAccelerated="false"和android:largeHeap="true",但崩溃仍未解决:
<application android:label="" android:name="${applicationName}" android:hardwareAccelerated="false" android:icon="@mipmap/ic_launcher" android:enableOnBackInvokedCallback="false" android:largeHeap="true" android:usesCleartextTraffic="true" android:roundIcon="@mipmap/ic_launcher_round">
Feed流视频播放器代码(由InviewNotifier控制,play为true时播放,false时暂停):
class _VideoWidgetState extends State<VideoWidget> { late VideoPlayerController _controller; ChewieController? _chewieController; @override void initState() { super.initState(); _initVideoPlayer(); } @override void didUpdateWidget(VideoWidget oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.play != widget.play) { if (_chewieController != null) { if (widget.play) { _chewieController!.play(); } else { _chewieController!.pause(); _clearCacheIfNeeded(); } } } } void _initVideoPlayer() async { try { _controller = VideoPlayerController.networkUrl(Uri.parse(widget.url)); await _controller.initialize(); _chewieController = ChewieController( videoPlayerController: _controller, autoInitialize: true, autoPlay: widget.play, looping: true, allowFullScreen: false, showControls: false, placeholder: const Center( child: CircularProgressIndicator(), ), ); setState(() {}); print("Controller initialized"); } catch (e) { print("Error initializing video: $e"); } } @override void dispose() { _chewieController?.dispose(); _controller.dispose(); _clearCacheIfNeeded(); super.dispose(); } void _clearCacheIfNeeded() { if (!widget.play) { final cacheManager = DefaultCacheManager(); cacheManager.emptyCache().then((value) { print("Cache cleared"); }); } } @override Widget build(BuildContext context) { return _chewieController != null && _chewieController!.videoPlayerController.value.hasError ? Center( child: CachedNetworkImage( imageUrl: widget.thumbnailUriLarge, errorWidget: (context, error, stackTrace) => CachedNetworkImage( imageUrl: widget.thumbnailUriSmall, errorWidget: (context, error, stackTrace) => const Center( child: Text("Temporary video is not available"), ), ), ), ) : _chewieController != null && _chewieController!.videoPlayerController.value.isInitialized ? AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Chewie( controller: _chewieController!, ), ) : Center( child: CachedNetworkImage( imageUrl: widget.thumbnailUriLarge, errorWidget: (context, error, stackTrace) => CachedNetworkImage( imageUrl: widget.thumbnailUriSmall, errorWidget: (context, error, stackTrace) => const Center( child: Text("Temporary video is not available"), ), ), ), ); } }
解决方案
修复播放器资源泄漏:当前视频暂停时仅调用
pause(),未释放控制器资源,Feed流列表频繁滚动会导致大量控制器实例堆积。修改didUpdateWidget逻辑,暂停时直接释放资源,播放时重新初始化:@override void didUpdateWidget(VideoWidget oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.play != widget.play) { if (widget.play) { // 播放时重新初始化控制器 _initVideoPlayer(); } else { // 暂停时彻底释放资源 _chewieController?.dispose(); _controller.dispose(); _chewieController = null; _clearCacheIfNeeded(); } } }优化缓存清理策略:
emptyCache()会清空全部缓存,过于激进且影响性能。改为只清理当前视频的缓存,同时给缓存设置大小上限:// 自定义缓存管理器,限制缓存大小 class CustomCacheManager extends CacheManager { static const key = "videoCache"; static CustomCacheManager? _instance; factory CustomCacheManager() { _instance ??= CustomCacheManager._(); return _instance!; } CustomCacheManager._() : super(Config( key, stalePeriod: const Duration(days: 7), maxNrOfCacheObjects: 30, // 限制缓存文件数量 maxCacheSize: 80 * 1024 * 1024, // 80MB缓存上限 )); } // 修改清理逻辑 void _clearCacheIfNeeded() { if (!widget.play) { final cacheManager = CustomCacheManager(); cacheManager.removeFile(Uri.parse(widget.url)).then((_) { print("Current video cache cleared"); }); } }开启硬件加速:关闭硬件加速会让CPU承担视频解码,增加内存占用。将Manifest中的
android:hardwareAccelerated改为true,让GPU处理解码任务:android:hardwareAccelerated="true"限制同时播放的视频数量:添加逻辑确保同一时刻只有一个视频处于播放状态,其他视频直接暂停并释放资源,避免多视频同时占用内存。
用内存分析工具定位泄漏:使用Android Studio Profiler生成内存快照,检查是否存在未释放的控制器实例或其他内存泄漏点,针对性修复。
内容的提问来源于stack exchange,提问作者Harsh Navapara
相关产品推荐
相关产品推荐

