Android.os.FileUriExposedException: archivo: ///storage/emulated/0/test.txt expuesto más allá de la aplicación a través de Intent.getData ()
La aplicación se bloquea al intentar abrir un archivo. Funciona debajo de Android N, pero en Android N se bloquea. Sólo se bloquea cuando intento abrir un archivo desde la tarjeta SD, no desde la partición del sistema. ¿Algún problema de permiso?
Código de muestra:
- ¿Por qué estoy recibiendo una NullPointerException de Android 7 Nougat?
- WebView se estrella al cargar vídeos para Android 7.0
- DP5 7.0 - ¿Causa la adición de extras a una intención pendiente?
- Cómo elegir la imagen de la cosecha de la cámara o la galería en Android 7.0?
- Android 7 Native Crash: libc.so tgkill
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
Iniciar sesión:
android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()
Editar:
Al orientar Android N, file://
URIs ya no están permitidos. Deberíamos usar content://
URIs en su lugar. Sin embargo, mi aplicación necesita abrir archivos en directorios raíz. ¿Algunas ideas?
- View.getLayoutParams (). Anchura que no funciona con el turrón
- "Lienzo: tratando de dibujar un mapa de bits demasiado grande" cuando el tamaño de pantalla Android N es mayor que el tamaño pequeño
- Android 7.0 Nougat recoge cadenas predeterminadas cuando el idioma del dispositivo es en_US
- Android N se bloquea en TextAppearanceSpan
- Cambios en el sistema de archivos de Android Nougat
- Abrir archivo descargado en Android N usando FileProvider
- BitmapFactory.decodeStream de Activos devuelve null en Android 7
- El diálogo datepicker no puede pasar a spinner en el dispositivo Android 7.0
Si su targetSdkVersion es 24 o superior, tenemos que usar la clase FileProvider para dar acceso al archivo o carpeta en particular para hacerlos accesibles para otras aplicaciones. Creamos nuestra propia clase heredando FileProvider
para asegurarnos de que FileProvider no entre en conflicto con FileProviders declarados en dependencias importadas como se describe aquí .
Pasos para reemplazar file: // uri con contenido: // uri:
-
Agregue una clase extendiendo FileProvider
public class GenericFileProvider extends FileProvider { }
-
Agregue una etiqueta FileProvider en AndroidManifest.xml bajo etiqueta. Especifique una autoridad única para el atributo
android:authorities
para evitar conflictos, las dependencias importadas podrían especificar${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=".GenericFileProvider" android:authorities="${applicationId}.my.package.name.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> </application> </manifest>
- A continuación, cree un archivo
provider_paths.xml
en la carpetaxml
en la carpetares
. La carpeta puede ser necesaria para crear si no existe. A continuación se muestra el contenido del archivo. Describe que nos gustaría compartir el acceso a la carpeta de almacenamiento externo en la carpeta raíz(path=".")
Con el nombre external_files .
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="."/> </paths>
-
El paso final es cambiar la línea de código abajo en
Uri photoURI = Uri.fromFile(createImageFile());
a
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
Espero que esto ayude … 🙂
Por favor, consulte, el código completo y la solución se ha explicado aquí.
Además de la solución que utiliza el FileProvider
, hay otra forma de evitar esto. Simplemente pon
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
En Application.onCreate()
. De esta forma, la VM ignora la exposición URI
del archivo.
Método
builder.detectFileUriExposure()
Activa la comprobación de la exposición del archivo, que también es el comportamiento predeterminado si no configuramos VmPolicy.
Me encontré con un problema que si utilizo un content://
URI
para enviar algo, algunas aplicaciones simplemente no pueden entenderlo. Y la degradación de la versión del target SDK
no está permitida. En este caso mi solución es útil.
Si su targetSdkVersion
es 24 o superior, no puede utilizar file:
valores de file:
Uri
en Intents
en dispositivos Android 7.0+ .
Sus opciones son:
-
Suelte su
targetSdkVersion
a 23 o menos, o -
Coloque su contenido en el almacenamiento interno, a continuación, utilice
FileProvider
para hacerlo disponible de forma selectiva a otras aplicaciones
Por ejemplo:
Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f)); i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(i);
(De este ejemplo de proyecto )
Primero tienes que añadir un proveedor a tu AndroidManifest
<application ...> <activity> .... </activity> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.your.package.fileProvider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
Ahora crear un archivo en la carpeta de recursos xml (si utiliza estudio android puede pulsar Alt + Intro después de resaltar los caminos_archivos y seleccione crear una opción de recurso xml)
A continuación, en el archivo file_paths ingrese
<?xml version="1.0" encoding="utf-8"?> <paths> <external-path path="Android/data/com.your.package/" name="files_root" /> <external-path path="." name="external_storage_root" /> </paths>
Este ejemplo es para la ruta externa que puede consultar aquí para más opciones. Esto le permitirá compartir archivos que están en esa carpeta y su subcarpeta.
Ahora todo lo que queda es crear la intención de la siguiente manera:
MimeTypeMap mime = MimeTypeMap.getSingleton(); String ext = newFile.getName().substring(newFile.getName().lastIndexOf(".") + 1); String type = mime.getMimeTypeFromExtension(ext); try { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.package.fileProvider", newFile); intent.setDataAndType(contentUri, type); } else { intent.setDataAndType(Uri.fromFile(newFile), type); } startActivityForResult(intent, ACTIVITY_VIEW_ATTACHMENT); } catch (ActivityNotFoundException anfe) { Toast.makeText(getContext(), "No activity found to open this attachment.", Toast.LENGTH_LONG).show(); }
EDIT : He añadido la carpeta raíz de la tarjeta sd en el file_paths. He probado este código y funciona.
Si su aplicación apunta a la API 24+ y todavía desea / necesita usar file: // intents, puede usar hacky para deshabilitar la comprobación de tiempo de ejecución:
if(Build.VERSION.SDK_INT>=24){ try{ Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure"); m.invoke(null); }catch(Exception e){ e.printStackTrace(); } }
El método StrictMode.disableDeathOnFileUriExposure
está oculto y documentado como:
/** * Used by lame internal apps that haven't done the hard work to get * themselves off file:// Uris yet. */
El problema es que mi aplicación no es cojo, pero no quiere ser lisiado mediante el uso de contenido: / / intentos que no son entendidos por muchas aplicaciones por ahí. Por ejemplo, abrir el archivo mp3 con el contenido: // esquema ofrece mucho menos aplicaciones que cuando se abre el mismo archivo: // esquema. No quiero pagar por los fallos de diseño de Google limitando la funcionalidad de mi aplicación.
Google quiere que los desarrolladores utilicen el esquema de contenido, pero el sistema no está preparado para esto, durante años se hicieron aplicaciones para utilizar archivos no "contenido", los archivos se pueden editar y guardar de nuevo, mientras que los archivos servidos sobre el esquema de contenido no puede ser ¿ellos?).
@palash k respuesta es correcta y trabajado para los archivos de almacenamiento interno, pero en mi caso quiero abrir archivos de almacenamiento externo también, mi aplicación se estrelló al abrir el archivo de almacenamiento externo como sdcard y usb, pero logro resolver el problema mediante la modificación Provider_paths.xml de la respuesta aceptada
Cambie el provider_paths.xml como a continuación
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path path="Android/data/${applicationId}/" name="files_root" /> <root-path name="root" path="/" /> </paths>
Y en la clase java (No hay cambio como la respuesta aceptada sólo una pequeña edición)
Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)
Esto me ayuda a arreglar el bloqueo de archivos de almacenamiento externo, Espero que esto ayude a alguien que tiene el mismo problema que el mío 🙂
Usar el fileProvider es el camino a seguir. Pero puede utilizar esta solución sencilla:
ADVERTENCIA : Se solucionará en la próxima versión de Android: https://issuetracker.google.com/issues/37122890#comment4
reemplazar:
startActivity(intent);
por
startActivity(Intent.createChooser(intent, "Your title"));
Utilicé la respuesta de Palash dada arriba pero era algo incompleta, tuve que proporcionar el permiso como esto
Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", new File(path)); List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); } }else { uri = Uri.fromFile(new File(path)); } intent.setDataAndType(uri, "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);
Si targetSdkVersion es mayor que 24 , FileProvider se utiliza para conceder acceso.
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>
Y reemplazar
Uri uri = Uri.fromFile(fileImagePath);
a
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
Y usted es bueno para ir.
- Cómo agregar un fragmento a una actividad con una vista de contenido creada mediante programación
- Error :: archivos duplicados durante el envasado de APK