Expo管理的React Native安卓应用大文件Fetch转Blob报错求助
Expo React Native安卓大文件URI转Blob失败问题解决
环境与需求
- 环境:Expo管理的React Native安卓应用(内部分发)
- 需求:通过AWS SDK将本地MP4文件上传至S3存储桶,
AWS.S3.putObject(params)要求上传数据为Blob格式
问题描述
使用fetch或XMLHttpRequest将本地URI转换为Blob时,当文件大小超过约190MB,会触发network request failed错误,且错误发生在本地文件获取阶段,未进入AWS SDK的上传流程。
现有尝试代码
1. Fetch方式
const uploadFile = async (file) => { let blob let response try{ response = await fetch(location); } catch(e){console.log('fetch error', e)} try { blob = await response.blob(); } catch(e){console.log('Blobify Error:', e)} };
2. XMLHttpRequest方式
let makeBlob; try{ makeBlob = (url)=> { return new Promise((resolve, reject) => { var xhr = new XMLHttpRequest(); xhr.onerror = reject; xhr.onreadystatechange = () => { if (xhr.readyState === 4) { resolve(xhr.response); } }; xhr.open('GET', url); xhr.responseType = 'blob'; // convert type xhr.send(); }) } }catch(e){console.error('blob fail:', e)}
原因分析
- 内存限制触发错误:React Native(包括Expo)的
fetch/XMLHttpRequest在转换大文件为Blob时,会将整个文件加载到JS堆内存中。安卓应用的JS堆内存有配额限制,当文件超过190MB时,内存占用超出阈值,导致系统中断请求,抛出network request failed错误。 - Blob转换的内存开销:Blob对象会在内存中存储完整的文件二进制数据,大文件直接转换会瞬间耗尽可用内存,触发内存溢出相关的异常。
替代方案:分片上传(避免全量转Blob)
直接规避全量转换Blob的操作,使用Expo FileSystem读取文件分片,结合S3的分片上传接口createMultipartUpload实现大文件上传,既解决内存问题,也符合S3大文件上传的最佳实践。
代码示例
import * as FileSystem from 'expo-file-system'; import AWS from 'aws-sdk'; // 初始化S3客户端(需配置credentials) const s3Client = new AWS.S3({ accessKeyId: 'YOUR_ACCESS_KEY', secretAccessKey: 'YOUR_SECRET_KEY', region: 'YOUR_REGION' }); // 分片大小设置为5MB(可根据设备内存调整) const CHUNK_SIZE = 5 * 1024 * 1024; const uploadLargeFileToS3 = async (localUri, bucketName, s3Key) => { try { // 获取本地文件信息 const fileInfo = await FileSystem.getInfoAsync(localUri); const totalFileSize = fileInfo.size; const totalChunks = Math.ceil(totalFileSize / CHUNK_SIZE); // 初始化S3分片上传 const { UploadId } = await s3Client.createMultipartUpload({ Bucket: bucketName, Key: s3Key, ContentType: 'video/mp4' }).promise(); const uploadTasks = []; // 遍历分片,读取并上传 for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { const startPos = chunkIndex * CHUNK_SIZE; const endPos = Math.min(startPos + CHUNK_SIZE, totalFileSize); const chunkLength = endPos - startPos; // 读取分片的Base64编码数据 const chunkBase64 = await FileSystem.readAsStringAsync(localUri, { encoding: FileSystem.EncodingType.Base64, position: startPos, length: chunkLength }); // 转换为Uint8Array(S3 uploadPart接受的二进制格式) const chunkBuffer = Uint8Array.from(atob(chunkBase64), char => char.charCodeAt(0)); // 提交分片上传任务 const uploadTask = s3Client.uploadPart({ Bucket: bucketName, Key: s3Key, UploadId, PartNumber: chunkIndex + 1, Body: chunkBuffer }).promise(); uploadTasks.push({ task: uploadTask, partNumber: chunkIndex + 1 }); } // 等待所有分片上传完成,收集ETag const uploadedParts = await Promise.all(uploadTasks.map(async ({ task, partNumber }) => { const { ETag } = await task; return { ETag, PartNumber: partNumber }; })); // 完成分片上传 await s3Client.completeMultipartUpload({ Bucket: bucketName, Key: s3Key, UploadId, MultipartUpload: { Parts: uploadedParts } }).promise(); console.log('大文件上传完成'); } catch (error) { console.error('分片上传失败:', error); // 可选:失败时中止分片上传 await s3Client.abortMultipartUpload({ Bucket: bucketName, Key: s3Key, UploadId: error.UploadId || '' }).promise().catch(() => {}); } };
方案优势
- 避免一次性加载大文件到内存,降低OOM风险
- 符合S3大文件上传的官方推荐方案,支持断点续传(可扩展实现)
- 适配Expo环境的文件系统API,无需额外原生配置
内容的提问来源于stack exchange,提问作者JohnyClash
相关产品推荐
相关产品推荐

