Unity iOS使用Firebase PutStreamAsync上传大文件内存崩溃求助
解决方案与分析
一、解决PutStreamAsync内存飙升崩溃问题
你的内存暴涨问题核心是旧版Firebase Unity SDK的PutStreamAsync方法在iOS上会将整个文件流加载到内存中,而非真正流式分块上传,导致大文件直接撑爆内存。针对这个问题,可以尝试以下方案:
1. 使用带缓冲的分块流包装FileStream
通过BufferedStream限制单次读取的内存占用,强制分块处理:
public async Task<string> UploadTestVideo(TestExecution testExecution) { string videoPath = TestPathHelper.GetLocalVideoPath(testExecution); StorageReference folderRef = storageReference.Child($"{StoragePathTestExecutions}/{testExecution.UID}/{StoragePathVideoRecordings}"); StorageReference fileRef = folderRef.Child("recording.mp4"); TVLog.Debug(Tag, $"Upload video <{videoPath}> to CloudStorage at <{fileRef.Path}>"); try { MetadataChange newMetadata = new MetadataChange(); newMetadata.ContentType = "video/mp4"; // 用BufferedStream设置128KB缓冲,控制单次读取内存 using (FileStream fileStream = File.OpenRead(videoPath)) using (BufferedStream bufferedStream = new BufferedStream(fileStream, 1024 * 128)) { TVLog.Debug(Tag, "Opened buffered file stream for uploading..."); StorageMetadata metadata = await fileRef.PutStreamAsync(bufferedStream, newMetadata); TVLog.Debug(Tag, "Finished uploading video..."); string md5Hash = metadata.Md5Hash; TVLog.Debug(Tag, "video md5 hash = " + md5Hash); return md5Hash; } } catch(StorageException e){ TVLog.Exception(Tag, $"Exception uploading video {e.HttpResultCode} - {e.ErrorCode} - {e.Message}"); return null; } }
注:改用using语句自动释放流,比手动Close/Dispose更可靠,避免资源泄漏。
2. 手动实现分块上传
如果缓冲流效果不佳,可利用Firebase Storage的分块上传API,将文件分割为5MB左右的小块逐一上传,严格控制单块内存占用:
private const int ChunkSize = 5 * 1024 * 1024; // 5MB分块 public async Task<string> UploadLargeVideoInChunks(TestExecution testExecution) { string videoPath = TestPathHelper.GetLocalVideoPath(testExecution); StorageReference fileRef = storageReference.Child($"{StoragePathTestExecutions}/{testExecution.UID}/{StoragePathVideoRecordings}/recording.mp4"); string sessionUri = null; long totalBytes = new FileInfo(videoPath).Length; long uploadedBytes = 0; try { MetadataChange newMetadata = new MetadataChange(); newMetadata.ContentType = "video/mp4"; using (FileStream stream = File.OpenRead(videoPath)) { byte[] buffer = new byte[ChunkSize]; int bytesRead; while ((bytesRead = await stream.ReadAsync(buffer, 0, ChunkSize)) > 0) { // 处理最后一块可能不足ChunkSize的情况 byte[] chunk = bytesRead == ChunkSize ? buffer : buffer.Take(bytesRead).ToArray(); if (string.IsNullOrEmpty(sessionUri)) { // 第一块上传,获取会话URI用于续传 var snapshot = await fileRef.PutBytesAsync(chunk, newMetadata); sessionUri = snapshot.SessionUri; } else { // 后续分块上传,传入会话URI续传 await fileRef.PutBytesAsync(chunk, newMetadata, sessionUri); } uploadedBytes += bytesRead; TVLog.Debug(Tag, $"Uploaded {uploadedBytes}/{totalBytes} bytes"); } } // 上传完成后获取最终元数据 var finalMetadata = await fileRef.GetMetadataAsync(); return finalMetadata.Md5Hash; } catch(StorageException e) { TVLog.Exception(Tag, $"Chunk upload failed: {e.HttpResultCode} - {e.ErrorCode} - {e.Message}"); return null; } }
二、解决PutFileAsync的ErrorObjectNotFound错误
该错误通常是文件路径格式不兼容导致,旧版Firebase SDK的PutFileAsync无法直接识别Unity的沙箱路径,需转换为iOS原生URL格式:
1. 转换Unity路径为iOS原生URL
修改路径处理逻辑,将Unity路径转换为file://开头的URL字符串:
public async Task<string> UploadViaPutFile(TestExecution testExecution) { string videoPath = TestPathHelper.GetLocalVideoPath(testExecution); // 转换为iOS兼容的file URL,若路径含特殊字符需编码 string iosFileUrl = $"file://{Uri.EscapeDataString(videoPath)}"; StorageReference fileRef = storageReference.Child($"{StoragePathTestExecutions}/{testExecution.UID}/{StoragePathVideoRecordings}/recording.mp4"); try { MetadataChange newMetadata = new MetadataChange(); newMetadata.ContentType = "video/mp4"; StorageMetadata metadata = await fileRef.PutFileAsync(iosFileUrl, newMetadata); return metadata.Md5Hash; } catch(StorageException e) { TVLog.Exception(Tag, $"PutFile failed: {e.HttpResultCode} - {e.ErrorCode} - {e.Message}"); return null; } }
2. 验证文件权限与存在性
调用PutFileAsync前,先确认文件存在且有读取权限:
if (!File.Exists(videoPath)) { TVLog.Error(Tag, $"File not found at path: {videoPath}"); return null; } // 尝试读取文件头验证权限 try { using (var stream = File.OpenRead(videoPath)) { byte[] testBuffer = new byte[10]; stream.Read(testBuffer, 0, 10); } } catch (UnauthorizedAccessException) { TVLog.Error(Tag, "No permission to read the file"); return null; }
三、其他优化建议
- 降低内存基线:上传前调用
Resources.UnloadUnusedAssets()和GC.Collect(),清理Unity缓存,减少初始内存占用。 - 监听内存警告:在iOS端监听系统内存警告,收到警告时暂停上传,释放非必要资源后再继续。
- 尝试兼容SDK升级:若Unity Cloud Build允许调整Xcode版本,可尝试升级Firebase SDK到9.x以上版本,原生修复流式上传的内存问题。
内容的提问来源于stack exchange,提问作者gonza89
相关产品推荐
相关产品推荐

