Почему у меня нет разрешения на запись в 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 22
(иbuildToolsVersion '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.)