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

React Native中content:// URI实现大文件流式写入的解决方案求助

解决方案

针对React Native中使用react-native-scoped-storage获取的content:// URI无法通过rn-fetch-blob或react-native-fs流式写入大文件的问题,以下是可行的实现方案:

方案一:编写Android原生模块实现流式写入

由于content://是Android系统的内容URI,直接通过RN第三方库无法支持流式写入,需要借助原生层的ContentResolver来处理文件流。

步骤1:创建Android原生模块

在你的React Native项目的Android目录下,添加以下两个文件:

1.1 流式写入模块(ScopedStorageStreamWriterModule.java)

package com.your_app_package_name;

import android.content.ContentResolver;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableMap;
import java.io.FileOutputStream;
import java.io.IOException;

public class ScopedStorageStreamWriterModule extends ReactContextBaseJavaModule {
    private final ReactApplicationContext reactContext;

    public ScopedStorageStreamWriterModule(ReactApplicationContext context) {
        super(context);
        reactContext = context;
    }

    @NonNull
    @Override
    public String getName() {
        return "ScopedStorageStreamWriter";
    }

    // 在指定目录下创建新文件,返回文件的content:// URI
    @ReactMethod
    public void createFileInDirectory(String directoryUri, String fileName, Promise promise) {
        try {
            Uri dirUri = Uri.parse(directoryUri);
            ContentResolver resolver = reactContext.getContentResolver();
            // 插入空值创建新文件
            Uri fileUri = resolver.insert(dirUri, null);
            if (fileUri == null) {
                promise.reject("CREATE_FAILED", "无法在指定目录创建文件");
                return;
            }
            promise.resolve(fileUri.toString());
        } catch (Exception e) {
            promise.reject("CREATE_ERROR", e.getMessage());
        }
    }

    // 写入文件分块(以追加模式打开,避免覆盖已写内容)
    @ReactMethod
    public void writeChunk(String fileUri, ReadableMap chunkData, Promise promise) {
        try {
            Uri uri = Uri.parse(fileUri);
            ContentResolver resolver = reactContext.getContentResolver();
            ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "wa");
            if (pfd == null) {
                promise.reject("FILE_OPEN_FAILED", "无法打开文件描述符");
                return;
            }
            FileOutputStream outputStream = new FileOutputStream(pfd.getFileDescriptor());
            byte[] chunk = chunkData.getByteArray("data");
            if (chunk != null) {
                outputStream.write(chunk);
                outputStream.flush();
            }
            pfd.close();
            promise.resolve(null);
        } catch (IOException e) {
            promise.reject("WRITE_ERROR", e.getMessage());
        }
    }
}

1.2 注册模块(ScopedStorageStreamWriterPackage.java)

package com.your_app_package_name;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ScopedStorageStreamWriterPackage implements ReactPackage {
    @NonNull
    @Override
    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new ScopedStorageStreamWriterModule(reactContext));
        return modules;
    }

    @NonNull
    @Override
    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

1.3 配置Package

在MainApplication.java中注册该Package:

@Override
protected List<ReactPackage> getPackages() {
    @SuppressWarnings("UnnecessaryLocalVariable")
    List<ReactPackage> packages = new PackageList(this).getPackages();
    // 添加自定义Package
    packages.add(new ScopedStorageStreamWriterPackage());
    return packages;
}

步骤2:RN端调用原生模块

在RN代码中,通过NativeModules调用原生方法实现流式写入:

import { NativeModules } from 'react-native';
const { ScopedStorageStreamWriter } = NativeModules;

// 在用户选择的目录下创建目标文件
async function createTargetFile(directoryUri, fileName) {
  try {
    const fileUri = await ScopedStorageStreamWriter.createFileInDirectory(directoryUri, fileName);
    return fileUri;
  } catch (error) {
    console.error('创建文件失败:', error);
    throw error;
  }
}

// 写入单块二进制数据
async function writeFileChunk(fileUri, chunk) {
  try {
    await ScopedStorageStreamWriter.writeChunk(fileUri, { data: chunk });
  } catch (error) {
    console.error('写入分块失败:', error);
    throw error;
  }
}

// 流式写入大文件示例(以处理二进制数据流为例)
async function streamWriteLargeFile(selectedDirUri, fileName, dataStream) {
  const targetFileUri = await createTargetFile(selectedDirUri, fileName);
  
  // 遍历数据流分块写入
  for await (const chunk of dataStream) {
    await writeFileChunk(targetFileUri, chunk);
  }
  
  console.log('大文件写入完成');
}

方案二:使用Android DownloadManager处理网络大文件下载

如果你的大文件来自网络,可以直接使用Android原生的DownloadManager,将下载路径指定为用户选择的content://目录:

// 在ScopedStorageStreamWriterModule中添加下载方法
@ReactMethod
public void downloadFileToScopedDir(String url, String directoryUri, String fileName, Promise promise) {
    try {
        Uri downloadUri = Uri.parse(url);
        Uri dirUri = Uri.parse(directoryUri);
        ContentResolver resolver = reactContext.getContentResolver();
        
        // 创建下载目标文件的URI
        Uri fileUri = resolver.insert(dirUri, null);
        if (fileUri == null) {
            promise.reject("DOWNLOAD_ERROR", "无法创建下载目标文件");
            return;
        }
        
        DownloadManager.Request request = new DownloadManager.Request(downloadUri);
        request.setDestinationUri(fileUri);
        request.setTitle(fileName);
        
        DownloadManager manager = (DownloadManager) reactContext.getSystemService(Context.DOWNLOAD_SERVICE);
        long downloadId = manager.enqueue(request);
        promise.resolve(downloadId);
    } catch (Exception e) {
        promise.reject("DOWNLOAD_ERROR", e.getMessage());
    }
}

RN端调用该方法即可实现后台流式下载,无需手动处理分块逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 18:15:36