react native 安卓ios写入文件权限获取及下载示例

627 阅读2分钟

前提: app 需要下载 pdf 文件,在模拟器上成功但安卓真机无限失败,调试发现 pms-app 只有修改图片和视频权限,并未有文件权限。 排查问题记录

  1. expo api 文档及 rn 官方文档只标明需要 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限,通过 gpt了解到 expo 的FileSystemapi 获取不到安卓的External storage,需要使用三方库react-native-fs获取(三方库包安装及使用这里不赘述)

image.pngimage.png

  1. 使用RNFS.DownloadDirectoryPath(RNFS为react-native-fs 官方简称)获取安卓下载路径写入时无限报权限错误。查询Android permission api 文档发现在 Android Api>=30如果需要访问_分区存储中的外部存储_,需要获取[_MANAGE_EXTERNAL_STORAGE_](https://developer.android.google.cn/reference/android/Manifest.permission#MANAGE_EXTERNAL_STORAGE)权限,而_WRITE_EXTERNAL_STORAGE_权限只在低版本上可以使用。

image.pngimage.png

  1. AndroidManifest.xml文件加入以下权限即可让 app 获取所有文件权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
  tools:ignore="ScopedStorage" />
<queries>
  1. 附上 ios/android 下载示例代码

注意:1. ios 无法直接下载至“文件”管理器,需要使用sharing(分享)api 手动保存至“文件”; 2. android 需要获取权限,且下载路径如果不存在也会报错,需要手动创建。

import { PermissionsAndroid, Platform } from 'react-native';
import * as FileSystem from 'expo-file-system';
import * as Sharing from 'expo-sharing';
import * as RNFS from 'react-native-fs';

/**
 * 下载文件至本地
 * @param fileUrl 文件地址
 * @param fileName 文件名,需要带文件格式,如果没有默认 pdf
 */
export const saveFile = async (fileUrl: string, fileName: string = '') => {
  if (Platform.OS === 'ios') {
    try {
      Toast.showLoading('正在处理...');
      const _fileName = `${new Date().getTime()}.${(fileName).split('.').pop() || 'pdf'}`;
      // 不使用文件名是怕文件名有中文,ios17 以下会出现问题
      const { uri } = await FileSystem.downloadAsync(fileUrl, `${FileSystem.cacheDirectory}${_fileName}`);
      // 调用分享对话框
      const isAvailable = await Sharing.isAvailableAsync();
      if (!isAvailable) {
        $toast.show('保存失败,暂不支持该机型', { type: 'warning' });
        throw new Error('Sharing is not available on this device');
      }
      $toast.show('下载完成,请手动存储到“文件”');
      await Sharing.shareAsync(uri);
      Toast.hideLoading();
    } catch (error) {
      $toast.show('下载失败,请重试', { type: 'warning' });
      Toast.hideLoading();
    }
  } else {
    try {
      // 获取到安卓下载目录的绝对路径(仅限 Android 和 Windows)
      const DownloadDirectoryPath = RNFS.DownloadDirectoryPath;

      const downLoadPath = `${DownloadDirectoryPath}/pms/${fileName}`;

      const downLoadFn = () => {
        Toast.showLoading('正在处理...');
        RNFS.downloadFile({
          fromUrl: fileUrl,
          toFile: downLoadPath,
          progressDivider: 5,
        })
          .promise.then(() => {
            $toast.show('下载完成');
            Toast.hideLoading();
          })
          .catch(() => {
            $toast.show('下载失败,请重试', { type: 'warning' });
            Toast.hideLoading();
          });
      };

      // 请求权限
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
        {
          title: '请求权限',
          message: '应用需要写入权限才能保存文件',
          buttonNeutral: '稍后询问',
          buttonNegative: '取消',
          buttonPositive: '确定',
        },
      );
      if (granted === PermissionsAndroid.RESULTS.GRANTED) {
        // 需要判断文件路径是否存在
        RNFS.exists(downLoadPath).then((res) => {
          // 如果存在直接下载
          if (res) {
            downLoadFn();
          } else {
            // 否则创建文件夹
            RNFS.mkdir(`${DownloadDirectoryPath}/pms`).then(() => {
              downLoadFn();
            });
          }
        });
      } else {
        $toast.show('下载失败,请检查是否开启权限', { type: 'warning' });
      }
    } catch (err) {
      $toast.show('下载失败,请检查是否开启权限', { type: 'warning' });
    }
  }
};