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

React Native Android中RNFetchBlob文件分享崩溃及失败问题求助

React Native Android 10+ 分享PDF文件失败/崩溃问题解决

问题分析

  1. 直接分享file:// URI失败:Android 10+启用分区存储机制,WhatsApp、Teams等第三方应用无法直接访问你的应用私有目录或外部存储的file://格式URI,必须通过FileProvider生成content://格式的共享URI才能实现跨应用文件访问。
  2. 转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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 03:40:34