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

Flutter应用批量上传MP4至Firebase Cloud Storage时内存溢出崩溃求助

Flutter Firebase Storage批量上传MP4文件导致OutOfMemoryError的解决方案

问题背景

在Android设备的Flutter应用中,批量上传外部存储中的MP4文件至Firebase Cloud Storage时,上传15-19个文件后应用因OutOfMemoryError崩溃。测试环境为Android Pixel 3模拟器,使用54个约50MB的20秒MP4文件,连接Firebase Storage模拟器本地测试。同步、异步上传逻辑均触发该问题,实际项目使用队列结构(示例代码简化为Completer)。

复现代码

import 'dart:async';
import 'dart:io';

import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:path_provider/path_provider.dart';
import 'firebase_options.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  try {
    await FirebaseStorage.instance.useStorageEmulator('10.0.2.2', 9199);
  } catch (e) {
    print(e);
  }

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Firebase Upload Test'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () => uploadFromExternalDirectory(),
            child: const Text('Upload Assets'),
          ),
        ),
      ),
    );
  }


  void uploadFromExternalDirectory() async {
    try {
      final directory =
          await getExternalStorageDirectory();
      if (directory == null) {
        print('Error: Could not get external storage directory');
        return;
      }

      Directory videos = directory;

      final storage = FirebaseStorage.instance;

      // Iterate through files in the directory (synchronously)
      for (var entity in videos.listSync(recursive: false)) {
        print('Processing $entity');
        if (entity is File) {
          String fileName = entity.path;

          try {
            UploadTask task = storage.ref('test/$fileName').putFile(entity);

            // Use a Completer to create a Future that completes when the upload is done
            final Completer<void> completer = Completer<void>();

            final StreamSubscription<TaskSnapshot> streamSubscription =
                task.snapshotEvents.listen((TaskSnapshot snapshot) {
              double progress =
                  (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
              print('Upload progress for $fileName: $progress%');
            }, onError: (error) {
              completer
                  .completeError(error); // Complete the future with an error
            }, onDone: () {
              completer.complete(); // Complete the future successfully
            });

            // Await the Completer's future to wait for the upload to finish
            await completer.future;

            // Cancel the stream subscription after the task is complete
            await streamSubscription.cancel();

            print('Upload of $fileName complete!');
          } on FirebaseException catch (e) {
            print('Error uploading $fileName: $e');
          }
        }
      }
    } catch (e) {
      // Handle any potential errors that might occur
      print('An error occurred: $e');
    }
  }
}

崩溃日志

I/pload_issue_app(24622): Alloc concurrent copying GC freed 273(182KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 191MB/192MB, paused 28us,137us total 9.132ms
I/pload_issue_app(24622): WaitForGcToComplete blocked Alloc on Alloc for 9.821ms
I/pload_issue_app(24622): Starting a blocking GC Alloc
I/pload_issue_app(24622): WaitForGcToComplete blocked Alloc on Alloc for 9.805ms
I/pload_issue_app(24622): Starting a blocking GC Alloc
I/pload_issue_app(24622): Waiting for a blocking GC Alloc
I/pload_issue_app(24622): Clamp target GC heap from 215MB to 192MB
W/pload_issue_app(24622): Throwing OutOfMemoryError "Failed to allocate a 59 byte allocation with 88776 free bytes and 86KB until OOM, target footprint 201326592, growth limit 201326592; giving up on allocation because <1% of heap free after GC." (VmSize 19423944 kB)
I/pload_issue_app(24622): WaitForGcToComplete blocked Background on Alloc for 19.661ms
I/pload_issue_app(24622): Waiting for a blocking GC Alloc
I/pload_issue_app(24622): Forcing collection of SoftReferences for 16B allocation
I/pload_issue_app(24622): Waiting for a blocking GC Alloc
E/AndroidRuntime(24622): FATAL EXCEPTION: main
E/AndroidRuntime(24622): Process: com.example.upload_issue_app, PID: 24622
E/AndroidRuntime(24622): java.lang.OutOfMemoryError: Failed to allocate a 59 byte allocation with 88776 free bytes and 86KB until OOM, target footprint 201326592, growth limit 201326592; giving up on allocation because <1% of heap free after GC.
E/AndroidRuntime(24622):    at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
E/AndroidRuntime(24622):    at java.nio.DirectByteBuffer$MemoryRef.<init>(DirectByteBuffer.java:71)
E/AndroidRuntime(24622):    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:265)
E/AndroidRuntime(24622):    at io.flutter.plugin.common.StandardMessageCodec.encodeMessage(StandardMessageCodec.java:77)
E/AndroidRuntime(24622):    at io.flutter.plugin.common.BasicMessageChannel$IncomingMessageHandler$1.reply(BasicMessageChannel.java:266)
E/AndroidRuntime(24622):    at io.flutter.plugins.firebase.storage.GeneratedAndroidFirebaseStorage$FirebaseStorageHostApi$14.success(GeneratedAndroidFirebaseStorage.java:1312)
E/AndroidRuntime(24622):    at io.flutter.plugins.firebase.storage.GeneratedAndroidFirebaseStorage$FirebaseStorageHostApi$14.success(GeneratedAndroidFirebaseStorage.java:1309)
E/AndroidRuntime(24622):    at io.flutter.plugins.firebase.storage.FlutterFirebaseStoragePlugin.referencePutFile(FlutterFirebaseStoragePlugin.java:509)
E/AndroidRuntime(24622):    at io.flutter.plugins.firebase.storage.GeneratedAndroidFirebaseStorage$FirebaseStorageHostApi$-CC.lambda$setup$13(GeneratedAndroidFirebaseStorage.java:1321)
E/AndroidRuntime(24622):    at io.flutter.plugins.firebase.storage.GeneratedAndroidFirebaseStorage$FirebaseStorageHostApi$$ExternalSyntheticLambda13.onMessage(Unknown Source:2)
E/AndroidRuntime(24622):    at io.flutter.plugin.common.BasicMessageChannel$IncomingMessageHandler.onMessage(BasicMessageChannel.java:261)
E/AndroidRuntime(24622):    at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
E/AndroidRuntime(24622):    at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:322)
E/AndroidRuntime(24622):    at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(Unknown Source:12)
E/AndroidRuntime(24622):    at android.os.Handler.handleCallback(Handler.java:958)
E/AndroidRuntime(24622):    at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(24622):    at android.os.Looper.loopOnce(Looper.java:205)
E/AndroidRuntime(24622):    at android.os.Looper.loop(Looper.java:294)
E/AndroidRuntime(24622):    at android.app.ActivityThread.main(ActivityThread.java:8177)
E/AndroidRuntime(24622):    at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(24622):    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
E/AndroidRuntime(24622):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
I/pload_issue_app(24622): WaitForGcToComplete blocked Alloc on Background for 33.559ms
I/pload_issue_app(24622): Starting a blocking GC Alloc
I/pload_issue_app(24622): WaitForGcToComplete blocked Alloc on Background for 19.223ms
I/pload_issue_app(24622): Starting a blocking GC Alloc
I/pload_issue_app(24622): Clamp target GC heap from 215MB to 192MB
I/pload_issue_app(24622): Alloc concurrent copying GC freed 568(58KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 191MB/192MB, paused 21us,11us total 10.624ms
I/pload_issue_app(24622): WaitForGcToComplete blocked Background on Alloc for 12.056ms
I/Process (24622): Sending signal. PID: 24622 SIG: 9
Lost connection to device.

Exited.

已尝试的方案

  • 同步/异步上传逻辑,均触发OOM
  • 改用putData方法,内存占用暴涨,崩溃更快;Flutter版无putStream方法
  • 通过Flutter和Android内存分析工具发现:Flutter RSS持续增长,Android侧Java和Native内存周期性上升

解决方案建议

1. 优化上传任务的资源释放

  • 上传完成后主动释放File对象引用,帮助GC回收内存:
    // 每个文件上传完成后
    entity = null;
    // 手动触发GC(可选,加速内存回收)
    System.runFinalization();
    
  • 确保错误路径下也取消StreamSubscription,避免资源泄漏:
    StreamSubscription<TaskSnapshot>? streamSubscription;
    try {
      UploadTask task = storage.ref('test/$fileName').putFile(entity);
      streamSubscription = task.snapshotEvents.listen(...);
      await completer.future;
      await streamSubscription.cancel();
    } catch (e) {
      await streamSubscription?.cancel();
      print('Error uploading $fileName: $e');
    }
    

2. 限制并发上传数量

即使串行上传,底层Native侧资源可能未及时释放,通过Semaphore控制并发数(如最多2个):

// 引入semaphore依赖或自行实现
final semaphore = Semaphore(2);
for (var entity in videos.listSync(recursive: false)) {
  if (entity is File) {
    await semaphore.acquire();
    try {
      // 上传逻辑
    } finally {
      semaphore.release();
      entity = null;
    }
  }
}

3. 通过Method Channel调用Android原生Firebase Storage API

Flutter插件可能存在内存泄漏,直接调用原生putFile方法,原生侧资源管理更高效:

  • 原生侧Kotlin代码:
    class FirebaseUploadHandler(private val context: Context) {
        private val storage = FirebaseStorage.getInstance()
    
        fun uploadFile(filePath: String, storagePath: String, result: MethodChannel.Result) {
            val file = File(filePath)
            val ref = storage.reference.child(storagePath)
            val uploadTask = ref.putFile(Uri.fromFile(file))
    
            uploadTask.addOnCompleteListener { task ->
                if (task.isSuccessful) {
                    result.success("Upload completed")
                } else {
                    result.error("UPLOAD_FAILED", task.exception?.message, null)
                }
            }
        }
    }
    
  • Flutter侧调用代码:
    const platform = MethodChannel('com.example.firebase_upload');
    
    Future<void> uploadFileNative(String filePath, String storagePath) async {
      try {
        await platform.invokeMethod('uploadFile', {
          'filePath': filePath,
          'storagePath': storagePath,
        });
      } on PlatformException catch (e) {
        print("Native upload error: ${e.message}");
      }
    }
    

4. 调整Android应用内存配置

在android/app/build.gradle中增加堆内存,缓解内存压力:

android {
    defaultConfig {
        multiDexEnabled true
        javaMaxHeapSize "512m"
    }
}

5. 更新Firebase Storage插件版本

使用最新版firebase_storage,官方可能已修复内存泄漏问题:

dependencies:
  firebase_storage: ^latest_version

内容的提问来源于stack exchange,提问作者Dana Lambert

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 11:17:37