android - studio - ¿Por qué no tengo permiso para escribir en el directorio de aplicaciones en el almacenamiento externo?
guardar datos en android studio (2)
Aprendí más sobre este problema, y es lo suficientemente diferente de la respuesta de CommonsWare que creo que vale la pena una nueva respuesta.
- Lo que hizo la diferencia para mi escenario original fue la tarjeta SD: si una tarjeta ya tenía una carpeta
/Android/data/com.example.myapp
que no se creó en este teléfono , entonces la aplicación podría tener problemas para obtener permiso para escribir a esa carpeta. Mientras que si la carpeta no existiera, la aplicación podría crearla y escribir en ella. Al menos en KitKat y después.- Esto es apoyado por algo explicado en este artículo :
... comenzando con el nivel de API 19 [KitKat], READ_EXTERNAL_STORAGE ya no tenía que acceder a los archivos ubicados en el almacenamiento externo, siempre que la carpeta de datos creada por el demonio FUSE coincida con el nombre del paquete de la aplicación. FUSE manejaría la síntesis del propietario, el grupo y los modos de archivos en el almacenamiento externo cuando se instala una aplicación [énfasis agregado].
- Así que esto confirma la suposición de que el problema surgió porque creé la carpeta de datos de la aplicación manualmente, en lugar de dejar que Android la configurara como fuera necesario. Investigación futura: en lugar de buscar solo permisos de aplicación como WRITE_EXTERNAL_STORAGE, también verifique las configuraciones de usuario, grupo y modo de la carpeta de datos de la aplicación en el sistema de archivos: tanto cuando se crean manualmente como cuando se crean mediante la instalación de la aplicación. ¡Compare el contraste! Tal vez esto proporcione suficiente información para permitir que la carpeta de datos de la aplicación se cree manualmente y aún funcione. Tenga en cuenta que hay una capa de envoltorio / emulación sobre el sistema de archivos FAT32 de la tarjeta SD, por lo que debemos considerar ambas capas del sistema de archivos.
- Esto es apoyado por algo explicado en este artículo :
- En un escenario posterior, encontré que es necesario que la aplicación llame a
context.getExternalFilesDirs(null)
para que se/Android/data/com.example.myapp
carpeta/Android/data/com.example.myapp
en la tarjeta SD. (Al menos, en Android 5.1 Lollipop y versiones posteriores. Necesito probar eso en KitKat).
El resumen de preguntas de TL; DR: Mi aplicación Android intenta escribir en el directorio de almacenamiento externo de la aplicación en una tarjeta SD. Falla con un error de permisos . Pero el mismo código (método), extraído en una aplicación de prueba mínima, ¡tiene éxito!
Dado que nuestro nivel de API objetivo incluye KitKat y posteriores (así como JellyBean), y KitKat restringe la escritura de aplicaciones en cualquier lugar de la tarjeta SD, excepto en el directorio de almacenamiento externo designado de la aplicación, la aplicación intenta escribir en el directorio designado, /path/to/sdcard/Android/data/com.example.myapp/files
. Verifico la ruta de este directorio obteniendo una lista de directorios de Activity.getExternalFilesDirs(null);
y encontrando uno que sea isRemovable()
. Es decir, no estamos codificando la ruta a la tarjeta SD, ya que varía según el fabricante y el dispositivo. Aquí está el código que demuestra el problema:
// 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);
}
}
El método checkDir () no es tan relevante, pero lo incluiré aquí para completar. Simplemente se asegura de que el directorio esté en el almacenamiento extraíble que está montado y registra otras propiedades del directorio (existe, se puede escribir).
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)));
}
En la aplicación de prueba (que se ejecuta en Android 5.1.1), la siguiente salida de registro muestra que el código funciona bien:
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
Así que el archivo fue creado con éxito. Pero en mi aplicación real (que también se ejecuta en Android 5.1.1), la llamada a createNewFile()
falla con un error de permisos:
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)
...
Antes de marcar esto como un duplicado: he leído varias otras preguntas sobre SO que describen los fallos de permisos al escribir en una tarjeta SD con KitKat o posterior. Pero ninguna de las causas o soluciones dadas parecen aplicarse a esta situación:
- El dispositivo no está conectado como almacenamiento masivo . Lo comprobé dos veces. Sin embargo, MTP está activado. (No puedo apagarlo sin desconectar el cable USB que uso para ver los registros).
- Mi manifiesto incluye
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- Estoy apuntando a la API de nivel 22, por lo que no debería tener que solicitar un permiso en tiempo de ejecución (a la Marshmallow ). El build.gradle tiene
targetSdkVersion 22
(ybuildToolsVersion ''21.1.2''
,compileSdkVersion 23
). - Estoy corriendo en KitKat y Lollipop; Ni siquiera tengo un dispositivo Marshmallow. Entonces, nuevamente, no debería tener que solicitar permiso en tiempo de ejecución, incluso si estuviera apuntando al nivel 23 de API.
- Como se mencionó anteriormente, estoy escribiendo en el directorio de almacenamiento externo designado que mi aplicación debe poder escribir, incluso en KitKat.
- El almacenamiento externo está montado ; El código lo verifica.
Resumen de cuándo funciona y cuándo no:
- En mi dispositivo pre-KitKat, tanto la aplicación de prueba como la aplicación real funcionan bien. Crearon con éxito un archivo en el directorio de la aplicación en la tarjeta SD.
- En mis dispositivos KitKat y Lollipop, la aplicación de prueba funciona bien pero la aplicación real no. En su lugar, muestra el error en el registro anterior.
¿Qué diferencia hay entre la aplicación de prueba y la aplicación real? Bueno, obviamente la aplicación real tiene muchas más cosas. Pero no puedo ver nada de lo que debería importar. Ambos tienen compileSdkVersion
, targetSdkVersion
, buildToolsVersion
, etc. buildToolsVersion
. Ambos también usan compile ''com.android.support:appcompat-v7:23.4.0''
como una dependencia.
Como una aplicación funcionó y otra no, la diferencia fue entre las aplicaciones, no con el dispositivo o la tarjeta. El directorio en cuestión no requiere ningún permiso de Android (por ejemplo, WRITE_EXTERNAL_STORAGE
). La única razón por la que no podría escribir en él sería si el sistema Android no hubiera configurado correctamente los permisos del sistema de archivos.
¿Es posible que yo mismo haya creado ese directorio y no haya configurado los permisos en algún lugar?
No estoy seguro de que pueda crear ese directorio usted mismo desde fuera de la aplicación y que funcione. Lo ideal sería que estuviera bien, pero no lo he probado y puedo ver dónde podría plantear problemas.
¿Hay alguna otra razón para no confiar en ello?
Dados los chanchullos del sistema de archivos que están ocurriendo, me pongo muy nervioso cuando los desarrolladores hacen suposiciones con respecto a la naturaleza de las rutas, eso es todo.