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

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)}

原因分析

  1. 内存限制触发错误:React Native(包括Expo)的fetch/XMLHttpRequest在转换大文件为Blob时,会将整个文件加载到JS堆内存中。安卓应用的JS堆内存有配额限制,当文件超过190MB时,内存占用超出阈值,导致系统中断请求,抛出network request failed错误。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 00:11:04