使用React Native FS将文件写入Android外部存储

anD*_*Dev 5 android react-native react-native-fs android-10.0

我的 React Native Android 应用程序需要将 JSON 文件保存在外部存储的特殊文件夹中。我尝试使用 RNFS ( https://github.com/itinance/react-native-fs ) 这样做:

const saveData = async () => {
    var path = `${RNFS.ExternalStorageDirectoryPath}/MyApp`;
    RNFS.mkdir(path);
    path += '/data.json';
    RNFS.writeFile(path, JSON.stringify(getData()), 'utf8')
      .then((success) => {
        console.log('Success');
      })
      .catch((err) => {
        console.log(err.message);
      });
  }
Run Code Online (Sandbox Code Playgroud)

它运行良好,但在 Android Q 设备上失败。显示此错误:

Error: Directory could not be created

如果我尝试编写一个普通文件而不创建目录,则会抛出此错误:

ENOENT: open failed: ENOENT (No such file or directory), open '/storage/emulated/0/data.json'

但是,我已将此权限添加到我的AndroidManifest.xml

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Run Code Online (Sandbox Code Playgroud)

并在设置中授予外部存储权限。但如果我将其更改RNFS.ExternalStorageDirectoryPathRNFS.DocumentDirectoryPath它,它就不会出现任何错误。但我需要访问外部存储。有什么办法可以做到吗?

anD*_*Dev 5

我发现 Android API 29+ 需要旧版外部存储访问。所以,我已经编辑了我的AndroidManifest.xml(位于android/app/src/main/),如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.appName">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application
    ...
      android:requestLegacyExternalStorage="true"
    ...
    >
    </application>
</manifest>
Run Code Online (Sandbox Code Playgroud)

一切都开始运转了。另外,我还添加了授予该saveData函数权限的请求:

const saveData = async () => {
    try {
      const granted = await PermissionsAndroid.requestMultiple([
        PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
        PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
      ]);
    } catch (err) {
      console.warn(err);
    }
    const readGranted = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE); 
    const writeGranted = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE);
    if(!readGranted || !writeGranted) {
      console.log('Read and write permissions have not been granted');
      return;
    }
    var path = `${RNFS.ExternalStorageDirectoryPath}/MyApp`;
    RNFS.mkdir(path);
    path += '/data.json';
    RNFS.writeFile(path, JSON.stringify(getData()), 'utf8')
      .then((success) => {
        console.log('Success');
      })
      .catch((err) => {
        console.log(err.message);
      });
  }
Run Code Online (Sandbox Code Playgroud)


Iqb*_*oso 5

更新:使用范围存储

或者,我找到了这个库react-native-file-access,它使用Scoped Storage。就我而言,我将文件存储在下载目录中。

首先,使用 RNSF,我们需要下载文件并将目的地设置为,然后使用react-native-file-accessRNFS.TemporaryDirectoryPath将下载的文件复制到特定文件夹(在我的例子中为下载)。

FileSystem.cpExternal(localFileUrl, `${filenameFormatted}`,'downloads')
Run Code Online (Sandbox Code Playgroud)

也许您可以使用这个库将文件存储在您的特定目录中,并进行一些调整

使用权限管理外部存储(不推荐):

在Android 11及更高版本中,访问存储的权限已更改。您必须添加名为MANAGE_EXTERNAL_STORAGE的附加权限,请参阅这些帖子:

https://developer.android.com/about/versions/11/privacy/storage#directory-access https://developer.android.com/training/data-storage/manage-all-files

将其添加到您的 AndroidManifest.xml 中

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
Run Code Online (Sandbox Code Playgroud)

我根据这篇文章创建简单的权限请求,从 React Native 调用这个本机函数。

创建PermissionFileModule.javaapp/src/main/<your_package>

public class PermissionFileModule extends ReactContextBaseJavaModule implements ActivityEventListener {

    public PermissionFileModule(@Nullable ReactApplicationContext reactContext) {
        super(reactContext);
        reactContext.addActivityEventListener(this);
    }

    @NonNull
    @Override
    public String getName() {
        return "PermissionFile";
    }

    @ReactMethod
    public void checkAndGrantPermission(Callback errorCallback, Callback successCallback) {
        try {
            if (!checkPermission()) {
                requestPermission();
                successCallback.invoke(false);
            } else {
                successCallback.invoke(true);
            }
        } catch (IllegalViewOperationException e) {
            errorCallback.invoke(e.getMessage());
        }
    }

    private boolean checkPermission() {
        if (SDK_INT >= Build.VERSION_CODES.R) {
            return Environment.isExternalStorageManager();
        } else {
            int result = ContextCompat.checkSelfPermission(getReactApplicationContext(), READ_EXTERNAL_STORAGE);
            int result1 = ContextCompat.checkSelfPermission(getReactApplicationContext(), WRITE_EXTERNAL_STORAGE);
            return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
        }
    }

    private void requestPermission() {
        if (SDK_INT >= Build.VERSION_CODES.R) {
            try {
                Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                intent.addCategory("android.intent.category.DEFAULT");
                intent.setData(Uri.parse(String.format("package:%s",getReactApplicationContext().getPackageName())));
                getCurrentActivity().startActivityForResult(intent, 2296);
            } catch (Exception e) {
                Intent intent = new Intent();
                intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                getCurrentActivity().startActivityForResult(intent, 2296);
            }
        } else {
            //below android 11
            ActivityCompat.requestPermissions(getCurrentActivity(), new String[]{WRITE_EXTERNAL_STORAGE}, 100);
        }
    }

    @Override
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
        if (requestCode == 2296) {
            if (SDK_INT >= Build.VERSION_CODES.R) {
                if (Environment.isExternalStorageManager()) {
                    Toast.makeText(getReactApplicationContext(), "Access granted", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(getReactApplicationContext(), "Access not granted", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

    @Override
    public void onNewIntent(Intent intent) {
        // do nothing
    }
}
Run Code Online (Sandbox Code Playgroud)

创建PermissionFilePackage.javaapp/src/main/<your_package>

public class PermissionFilePackage implements ReactPackage {
    @NonNull
    @Override
    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

        modules.add(new PermissionFileModule(reactContext));
        return modules;
    }

    @NonNull
    @Override
    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}
Run Code Online (Sandbox Code Playgroud)

MainApplication.java中,添加PermissionFilePackage.java作为附加包

...
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
    List<ReactPackage> packages = new PackageList(this).getPackages();
    // Packages that cannot be autolinked yet can be added manually here, for example:
    // packages.add(new MyReactNativePackage());
    packages.add(new PermissionFilePackage());
    return packages;
}
...
Run Code Online (Sandbox Code Playgroud)

在你的 RN 组件中,像这样调用权限文件

...
import {NativeModules} from 'react-native';
var PermissionFile = NativeModules.PermissionFile;
...

if (Platform.Version >= 30) {
        PermissionFile.checkAndGrantPermission(
          (err) => {
            DeviceUtils.showAlert(
              'Sorry',
              'Access not granted',
            );
          },
          (res) => {
            if (res) {
              checkDirectoryAndDownload(url, name, ext);
            }
          },
        );
      } else {
        DeviceUtils.grantPermissionSingleAndroid(
          PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE,
          (isAllow) => {
            if (isAllow) {
              checkDirectoryAndDownload(url, name, ext);
            } else {
              DeviceUtils.showAlert(
                'Sorry',
                'Access not granted',
              );
            }
          },
        );
      }
Run Code Online (Sandbox Code Playgroud)