android.os.FileUriExposedException: file:///storage/emulated/0/test.txt expuesto más allá de la aplicación a través de Intent.getData()
La aplicación falla cuando intento abrir un archivo. Funciona bajo Android Nougat, pero en Android Nougat falla. Sólo falla cuando intento abrir un archivo desde la tarjeta SD, no desde la partición del sistema. ¿Algún problema de permisos?
Código de muestra:
File file = new File("/storage/emulated/0/test.txt");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "text/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); // Crashes on this line
Registro:
android.os.FileUriExposedException: file:///storage/emulated/0/test.txt expuesto más allá de la aplicación a través de Intent.getData()
Editar:
Cuando se orienta a Android Nougat, file://
ya no se permiten URI. Deberíamos usar content://
URI en su lugar. Sin embargo, mi aplicación necesita abrir archivos en directorios raíz. ¿Algunas ideas?
Si es así targetSdkVersion >= 24
, entonces tenemos que usar FileProvider
la clase para dar acceso al archivo o carpeta en particular para que otras aplicaciones puedan acceder a ellos. Creamos nuestra propia herencia de clases FileProvider
para asegurarnos de que nuestro FileProvider no entre en conflicto con los FileProviders declarados en dependencias importadas como se describe aquí .
Pasos para reemplazar file://
URI con content://
URI:
- Agregue una
<provider>
etiqueta FileProvider debajo deAndroidManifest.xml
la<application>
etiqueta. Especifique una autoridad única para elandroid:authorities
atributo para evitar conflictos, podrían especificar dependencias importadas${applicationId}.provider
y otras autoridades de uso común.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>
- Luego cree un
provider_paths.xml
archivo enres/xml
la carpeta. Es posible que sea necesario crear una carpeta si aún no existe. El contenido del archivo se muestra a continuación. Describe que nos gustaría compartir el acceso al almacenamiento externo en la carpeta raíz(path=".")
con el nombre external_files .
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>
El último paso es cambiar la línea de código siguiente en
Uri photoURI = Uri.fromFile(createImageFile());
a
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", createImageFile());
Editar: si está utilizando la intención de hacer que el sistema abra su archivo, es posible que deba agregar la siguiente línea de código:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Consulte el código completo y la solución que se explican aquí.
Además de la solución que utiliza FileProvider
, existe otra forma de solucionar este problema. Simplemente pon
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
en Application.onCreate()
. De esta manera, la VM ignora la URI
exposición del archivo.
Método
builder.detectFileUriExposure()
habilita la verificación de exposición de archivos, que también es el comportamiento predeterminado si no configuramos una VmPolicy.
Encontré un problema que si uso content://
URI
para enviar algo, algunas aplicaciones simplemente no pueden entenderlo. Y target SDK
no se permite degradar la versión. En este caso mi solución es útil.
Actualizar:
Como se menciona en el comentario, StrictMode es una herramienta de diagnóstico y no debe usarse para este problema. Cuando publiqué esta respuesta hace un año, muchas aplicaciones solo pueden recibir File uris. Simplemente fallan cuando intenté enviarles una uri de FileProvider. Esto ya está solucionado en la mayoría de las aplicaciones, por lo que deberíamos optar por la solución FileProvider.
Si targetSdkVersion
es superior a 24 , se utiliza FileProvider para otorgar acceso.
Cree un archivo xml (Ruta: res\xml) proveedor_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
Agregar un proveedor en AndroidManifest.xml
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
Si está utilizando androidx , la ruta de FileProvider debe ser:
android:name="androidx.core.content.FileProvider"
y reemplazar
Uri uri = Uri.fromFile(fileImagePath);
a
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
Editar: mientras incluye el URI con un Intent
asegúrese de agregar la siguiente línea:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
y estás listo para irte.