Записывать файлы во внешнее хранилище Android с помощью React Native FS

Приложение My 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);
      });
  }

Он работает хорошо, но не работает на устройстве 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"/>

И предоставил разрешения на внешнее хранилище в настройках. Но если я изменю RNFS.ExternalStorageDirectoryPath на RNFS.DocumentDirectoryPath, он будет работать без ошибок. Но мне нужно получить доступ к внешнему хранилищу. Есть ли способ это сделать?


person anDev    schedule 13.08.2020    source источник
comment
Да, в Android Q getExternalStorageDirectory () больше не доступен. Вы можете читать это здесь каждый день. Также как использовать legacyExternalStorage в манифесте, чтобы он работал для Q. Но лучше использовать другой каталог, чтобы быть готовым для R.   -  person blackapps    schedule 13.08.2020
comment
RNFS.DocumentDirectoryPath Какой это был бы путь?   -  person blackapps    schedule 13.08.2020
comment
@blackapps, это data/user/0/com.appName/files/   -  person anDev    schedule 13.08.2020
comment
/data/user/0/com.appName/files/ будет из getFilesDir (). Личный каталог приложений. Тогда вам не нужно никакого разрешения.   -  person blackapps    schedule 13.08.2020
comment
But I need to get an access to the external storage. Объясните, пожалуйста, почему.   -  person blackapps    schedule 13.08.2020
comment
@blackapps, пользователь может создавать резервные копии и восстанавливать их. Файл резервной копии должен быть доступен для редактирования, перемещения или предоставления доступа пользователю.   -  person anDev    schedule 13.08.2020
comment
Ok. Вы не сказали, что пользователь будет использовать для этого файловый менеджер или другое приложение. Или ваше приложение предоставляет такую ​​возможность?   -  person blackapps    schedule 13.08.2020
comment
@blackapps, да, я сообщаю пользователю местоположение сохраненного файла, но он недоступен, потому что для папки данных требуются права суперпользователя. Хочу сделать во внешнем хранилище специальную папку, как в WhatsApp   -  person anDev    schedule 13.08.2020
comment
Вы не сказали мне, использует ли пользователь для этого файловый менеджер или приложение, отличное от вашего.   -  person blackapps    schedule 13.08.2020


Ответы (2)


Я обнаружил, что для доступа к устаревшему внешнему хранилищу требуется 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>

И все заработало. Также я добавил запрос на предоставление разрешений функции 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);
      });
  }
person anDev    schedule 13.08.2020
comment
Ok. Но это работает для 29. Не для 30, как вы предложили с 29+. Это только дает вам дополнительное время для решения вашей проблемы. - person blackapps; 13.08.2020
comment
@blackapps, что мне сделать, чтобы он работал на API 30? - person anDev; 13.08.2020
comment
Будет еще один RNSF.ExternalFilesPath или что-то в этом роде. Эквивалентно getExternalFilesDir (). Разрешения не требуются. - person blackapps; 13.08.2020

В Android версии 10 (уровень API 29) представлена ​​концепция хранилища с ограниченным объемом. Согласно Docs-

Чтобы предоставить пользователям больший контроль над своими файлами и ограничить беспорядок в файлах, приложениям, ориентированным на Android 10 (уровень API 29) и выше, по умолчанию предоставляется ограниченный доступ к внешнему хранилищу или хранилище с ограниченным объемом. Такие приложения имеют доступ только к каталогу конкретного приложения на внешнем хранилище, а также к определенным типам мультимедиа, созданным приложением.

Для Android версии 10 у вас есть возможность отказаться от ограниченного хранилища, добавив android:requestLegacyExternalStorage="true" в AndroidManifest.xml следующим образом:

<application ...
 android:requestLegacyExternalStorage="true"
...>
....
</application>

Это временное решение, оно будет работать только для Android версии 10, но не выше. Также убедитесь, что вы запрашиваете разрешение во время выполнения для чтения / записи внешнего хранилища.

person mohit arora    schedule 29.09.2020