Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
T treasure
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 12
    • Issues 12
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
  • Merge requests 0
    • Merge requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Operations
    • Operations
    • Incidents
    • Environments
  • Packages & Registries
    • Packages & Registries
    • Container Registry
  • Analytics
    • Analytics
    • CI/CD
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • External wiki
    • External wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • FE
  • treasure
  • Issues
  • #210

Closed
Open
Created Dec 19, 2023 by chenyu@CubiDeveloper

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

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

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

imageimage

  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 image

  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 需要获取权限,且下载路径如果不存在也会报错,需要手动创建。

/**
 * 下载文件至本地
 * @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' });
    }
  }
};
Edited Dec 20, 2023 by chenyu
Assignee
Assign to
None
Milestone
None
Assign milestone
Time tracking