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
相关产品推荐
相关产品推荐

