react native 安卓ios写入文件权限获取及下载示例
前提:
pms-app 需要下载 pdf 文件,在模拟器上成功但安卓真机无限失败,调试发现 pms-app 只有修改图片和视频权限,并未有文件权限。
排查问题记录
-
expo api 文档及 rn 官方文档只标明需要
READ_EXTERNAL_STORAGE
与WRITE_EXTERNAL_STORAGE
权限,通过 gpt了解到 expo 的FileSystem
api 获取不到安卓的External storage
,需要使用三方库react-native-fs
获取(三方库包安装及使用这里不赘述)
- 使用
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_
权限只在低版本上可以使用。
- 在
AndroidManifest.xml
文件加入以下权限即可让 app 获取所有文件权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<queries>
- 附上 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' });
}
}
};