React Native Android中RNFetchBlob文件分享崩溃及失败问题求助
React Native Android 10+ 分享PDF文件失败/崩溃问题解决
问题分析
- 直接分享file:// URI失败:Android 10+启用分区存储机制,WhatsApp、Teams等第三方应用无法直接访问你的应用私有目录或外部存储的
file://格式URI,必须通过FileProvider生成content://格式的共享URI才能实现跨应用文件访问。 - 转Base64崩溃:
readFile('base64')会将整个PDF文件一次性加载到内存,若文件体积较大,直接触发内存溢出(OOM)导致应用崩溃。
解决方案
一、配置FileProvider(解决分享失败核心问题)
1. 配置AndroidManifest.xml
在<application>标签内添加FileProvider声明:
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
其中${applicationId}会自动替换为你的应用包名(如com.your.app)。
2. 创建文件路径配置文件
在android/app/src/main/res/xml/目录下新建file_paths.xml(若xml目录不存在则手动创建),内容如下:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <downloads-path name="downloads" path="." /> <external-path name="external_files" path="." /> </paths>
该配置允许FileProvider访问设备下载目录及外部存储根目录。
3. 修改代码生成content:// URI
调整下载后的分享逻辑,替换file:// URI为content://格式:
import { Platform, PermissionsAndroid, NativeModules } from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; import Share from 'react-native-share'; async function func_1(pdfUrl: string) { let filePath: string | null = null; try { // Android 10+ 无需申请存储权限,分区存储默认允许访问公共下载目录 if (Platform.OS === 'android' && Platform.Version < 29) { const result = await PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, ]); const hasPermission = result[PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE] === PermissionsAndroid.RESULTS.GRANTED; if (!hasPermission) return; } const dirs = RNFetchBlob.fs.dirs; const dynamicFileName = `FileName_123.pdf`; const downloadPath = Platform.OS === 'ios' ? `${dirs.DocumentDir || dirs.MainBundleDir}/${dynamicFileName}` : `${dirs.DownloadDir}/${dynamicFileName}`; const res = await RNFetchBlob.config({ fileCache: true, path: downloadPath, addAndroidDownloads: { title: dynamicFileName, useDownloadManager: true, notification: true, path: downloadPath, mime: 'application/pdf', description: 'File downloaded by download manager.', }, }).fetch('GET', encodeURI(pdfUrl), {}); filePath = res.path(); let shareUrl = filePath; if (Platform.OS === 'android') { // 通过原生方法生成content:// URI(无需额外依赖) shareUrl = await NativeModules.FileProviderModule.getUriForFile(filePath); } const shareOptions = { title: dynamicFileName, url: shareUrl, failOnCancel: false, showAppsToView: true, mimeType: 'application/pdf', }; await Share.open(shareOptions); } catch (err) { console.log(err); } }
补充:需要创建一个简单的原生模块FileProviderModule来调用FileProvider的API,步骤如下:
- 在
android/app/src/main/java/com/yourapp/下新建FileProviderModule.java:
package com.yourapp; // 替换为你的应用包名 import android.content.Context; import android.net.Uri; import androidx.core.content.FileProvider; 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 java.io.File; public class FileProviderModule extends ReactContextBaseJavaModule { private static final String MODULE_NAME = "FileProviderModule"; public FileProviderModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return MODULE_NAME; } @ReactMethod public void getUriForFile(String filePath, Promise promise) { try { Context context = getReactApplicationContext(); File file = new File(filePath); Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); promise.resolve(uri.toString()); } catch (Exception e) { promise.reject("FILE_PROVIDER_ERROR", e.getMessage()); } } }
- 在
MainApplication.java中注册模块:
@Override protected List<ReactPackage> getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List<ReactPackage> packages = new PackageList(this).getPackages(); packages.add(new FileProviderPackage()); // 添加这行 return packages; }
- 新建
FileProviderPackage.java:
package com.yourapp; // 替换为你的应用包名 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 FileProviderPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new FileProviderModule(reactContext)); return modules; } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } }
二、Base64崩溃的规避方案
不建议用Base64分享大文件,若必须尝试,需分块读取文件避免内存溢出:
RNFetchBlob.config(configOptions) .fetch('GET', pdfUrl) .then(async (resp) => { filePath = resp.path(); const fileStats = await RNFetchBlob.fs.stat(filePath); const chunkSize = 1024 * 1024; // 按1MB分块读取 let base64Data = ''; for (let offset = 0; offset < fileStats.size; offset += chunkSize) { const chunk = await resp.readFile('base64', offset, chunkSize); base64Data += chunk; } base64Data = `data:application/pdf;base64,${base64Data}`; await Share.open({ title: dynamicFileName, url: base64Data }); }) .catch((err) => { console.log(err); });
关键注意事项
- 确保
android:authorities值与原生模块中使用的包名后缀一致(即应用包名.fileprovider)。 - 测试时需等待文件完全下载完成后再触发分享,避免文件未写入完成导致的错误。
- Android 11+无需额外权限,FileProvider已满足跨应用文件共享需求。
内容的提问来源于stack exchange,提问作者christo-pher18
相关产品推荐
相关产品推荐

