Почему у меня нет разрешения на запись в app dir на внешнем хранилище?

резюме вопроса 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)
    ...

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

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

Сводка о том, когда он работает, а когда нет:

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

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

2 ответов


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

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

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

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

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


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

  • что изменило мой первоначальный сценарий, это SD-карты: если карта уже a о не создан на этом телефоне, то приложение может иметь проблемы при получении разрешения на запись в эту папку. А если папка не существует, приложение может создать его и написать его. По крайней мере, в Киткате и позже.
    • это поддерживается чем-то объясненным в в этой статье:

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

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