getexternalfilesdir - external storage android



Почему у меня нет разрешения на запись в каталог приложений на внешнем хранилище? (2)

Сводка вопроса TL; DR: мое приложение для Android пытается записать в каталог внешнего хранилища приложения на SD-карте Это терпит неудачу с ошибкой разрешений . Но тот же код (метод), извлеченный в минимальное тестовое приложение, успешен!

Поскольку наш целевой уровень API включает KitKat и более поздние версии (а также JellyBean), а KitKat ограничивает приложения от записи в любое место на SD-карте, кроме назначенного каталога внешнего хранилища приложения, приложение пытается выполнить запись в этот назначенный каталог /path/to/sdcard/Android/data/com.example.myapp/files . Я проверяю путь к этому каталогу, получая список каталогов из Activity.getExternalFilesDirs(null); и найти тот, который isRemovable() . Т.е. мы не жестко программируем путь к SD-карте, поскольку он зависит от производителя и устройства. Вот код, который демонстрирует проблему:

// Attempt to create a test file in dir.
private void testCreateFile(File dir) {
    Log.d(TAG, ">> Testing dir " + dir.getAbsolutePath());

    if (!checkDir(dir)) { return; }

    // Now actually try to create a file in this dir.
    File f = new File(dir, "foo.txt");
    try {
        boolean result = f.createNewFile();
        Log.d(TAG, String.format("Attempted to create file. No errors. Result: %b. Now exists: %b",
                result, f.exists()));
    } catch (Exception e) {
        Log.e(TAG, "Failed to create file " + f.getAbsolutePath(), e);
    }
}

Метод checkDir () не так актуален, но я приведу его здесь для полноты картины. Он просто удостоверяется, что каталог находится на съемном носителе, который смонтирован, и регистрирует другие свойства каталога (существует, доступно для записи).

private boolean checkDir(File dir) {
    boolean isRemovable = false;
    // Can't tell whether it's removable storage?
    boolean cantTell = false;
    String storageState = null;

    // Is this the primary external storage directory?
    boolean isPrimary = false;
    try {
        isPrimary = dir.getCanonicalPath()
                .startsWith(Environment.getExternalStorageDirectory().getCanonicalPath());
    } catch (IOException e) {
        isPrimary = dir.getAbsolutePath()
                .startsWith(Environment.getExternalStorageDirectory().getAbsolutePath());
    }

    if (isPrimary) {
        isRemovable = Environment.isExternalStorageRemovable();
        storageState = Environment.getExternalStorageState();
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // I actually use a try/catch for IllegalArgumentException here, but 
        // that doesn't affect this example.
        isRemovable = Environment.isExternalStorageRemovable(dir);
        storageState = Environment.getExternalStorageState(dir);
    } else {
        cantTell = true;
    }

    if (cantTell) {
        Log.d(TAG, String.format("  exists: %b  readable: %b  writeable: %b primary: %b  cantTell: %b",
                dir.exists(), dir.canRead(), dir.canWrite(), isPrimary, cantTell));
    } else {
        Log.d(TAG, String.format("  exists: %b  readable: %b  writeable: %b primary: %b  removable: %b  state: %s  cantTell: %b",
                dir.exists(), dir.canRead(), dir.canWrite(), isPrimary, isRemovable, storageState, cantTell));
    }

    return (cantTell || (isRemovable && storageState.equalsIgnoreCase(MEDIA_MOUNTED)));
}

В тестовом приложении (запущенном на Android 5.1.1) следующий вывод журнала показывает, что код работает нормально:

10-25 19:56:40 D/MainActivity: >> Testing dir /storage/extSdCard/Android/data/com.example.testapp/files
10-25 19:56:40 D/MainActivity:   exists: true  readable: true  writeable: true primary: false  removable: true  state: mounted  cantTell: false
10-25 19:56:40 D/MainActivity: Attempted to create file. No errors. Result: false. Now exists: true

Таким образом, файл был успешно создан. Но в моем реальном приложении (также работающем на Android 5.1.1) вызов createNewFile() завершается с ошибкой прав доступа:

10-25 18:14:56... D/LessonsDB: >> Testing dir /storage/extSdCard/Android/data/com.example.myapp/files
10-25 18:14:56... D/LessonsDB:   exists: true  readable: true  writeable: true primary: false  removable: true  state: mounted  cantTell: false
10-25 18:14:56... E/LessonsDB: Failed to create file /storage/extSdCard/Android/data/com.example.myapp/files/foo.txt
    java.io.IOException: open failed: EACCES (Permission denied)
        at java.io.File.createNewFile(File.java:941)
        at com.example.myapp.dmm.LessonsDB.testCreateFile(LessonsDB.java:169)
    ...
     Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
        at libcore.io.Posix.open(Native Method)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
        at java.io.File.createNewFile(File.java:934)
    ...

Прежде чем пометить это как дубликат: я прочитал несколько других вопросов о SO, описывающих сбои разрешений при записи на SD-карту в KitKat или более поздних версиях. Но ни одна из приведенных причин или решений не имеет отношения к этой ситуации:

  • Устройство не подключено в качестве запоминающего устройства . Я дважды проверил. Тем не менее, MTP включен. (Я не могу выключить его, не отсоединив USB-кабель, который я использую для просмотра журналов.)
  • Мой манифест включает в себя <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  • Я ориентируюсь на уровень API 22, поэтому мне не нужно запрашивать разрешение во время выполнения (а-ля Зефир ). У build.gradle есть targetSdkVersion 22buildToolsVersion '21.1.2' , compileSdkVersion 23 ).
  • Я бегу на KitKat и Lollipop; У меня даже нет устройства Зефир. Опять же, мне не нужно запрашивать разрешение во время выполнения, даже если я нацеливался на уровень API 23.
  • Как упоминалось выше, я пишу в назначенный каталог внешнего хранилища, который должен быть доступен для записи моим приложением, даже на KitKat.
  • Внешнее хранилище смонтировано ; код подтверждает это.

Резюме того, когда это работает, а когда нет:

  • На моем устройстве pre-KitKat тестовое приложение и реальное приложение работают нормально. Они успешно создают файл в директории приложения на SD-карте.
  • На моих устройствах KitKat и Lollipop тестовое приложение работает нормально, а настоящее приложение - нет. Вместо этого это показывает ошибку в вышеупомянутом журнале.

Какая разница между тестовым приложением и реальным приложением? Ну, очевидно, в настоящем приложении гораздо больше вещей. Но я не вижу ничего, что должно иметь значение. Оба имеют идентичные compileSdkVersion , targetSdkVersion , buildToolsVersion и т. Д. Оба также используют compile 'com.android.support:appcompat-v7:23.4.0' в качестве зависимости.

https://src-bin.com


Answer #1

Поскольку одно приложение работало, а другое - нет, разница была между приложениями, а не с устройством или картой. Указанный каталог не требует каких-либо разрешений Android (например, WRITE_EXTERNAL_STORAGE ). Единственная причина, по которой вы не сможете писать в нее, заключается в том, что система Android не настроила разрешения файловой системы должным образом.

Возможно, я сам создал этот каталог и не смог установить разрешения где-нибудь?

Я не уверен, что вы можете создать этот каталог самостоятельно вне приложения и заставить его работать. В идеале это было бы хорошо, но я не пробовал, и я вижу, где это может создать проблемы.

Есть ли еще одна причина не доверять этому?

Учитывая происходящие махинации с файловой системой, я очень нервничаю, когда разработчики делают предположения относительно природы путей, вот и все.


Answer #2

Я узнал больше об этой проблеме, и она достаточно отличается от ответа CommonsWare, и я думаю, что он заслуживает нового ответа.

  • В моем исходном сценарии была разница с SD-картами: если на карте уже была папка /Android/data/com.example.myapp которая не была создана на этом телефоне , то приложение может иметь проблемы с получением разрешения на запись. в эту папку. Принимая во внимание, что если папка не существует, приложение может создать ее и записать в нее. По крайней мере, в KitKat и позже.
    • Это подтверждается тем, что объясняется в этой статье :

      ... начиная с уровня API 19 [KitKat], READ_EXTERNAL_STORAGE больше не требовался для доступа к файлам, расположенным во внешнем хранилище - при условии, что папка данных, созданная демоном FUSE, совпадает с именем пакета приложения. FUSE будет обрабатывать синтез владельца, группы и режимы файлов на внешнем хранилище, когда приложение установлено [выделено добавлено].

    • Таким образом, это подтверждает предположение, что проблема возникла из-за того, что я создал папку данных приложения вручную, вместо того, чтобы позволить Android настроить его так, как нужно. Дальнейшие исследования: вместо просмотра только разрешений приложения, таких как WRITE_EXTERNAL_STORAGE, также проверьте настройки пользователя, группы и режима для папки данных приложения в файловой системе: как при создании вручную, так и при создании путем установки приложения. Сравнить контраст! Возможно, этого будет достаточно, чтобы папка данных приложения создавалась вручную и продолжала работать. Имейте в виду, что поверх файловой системы FAT32 SD-карты есть слой оболочки / эмуляции, поэтому нам необходимо рассмотреть оба уровня файловой системы.
  • В более позднем сценарии я обнаружил, что приложению необходимо вызвать context.getExternalFilesDirs(null) для того, чтобы на SD-карте была создана папка /Android/data/com.example.myapp . (По крайней мере, на Android 5.1 Lollipop и позже. Необходимо проверить это на KitKat.)




android-external-storage