android.os.FileUriExposedException: file: ///storage/emulated/0/test.txt expuesta más allá de la aplicación a través de Intent.getData ()

La aplicación se cuelga cuando bash abrir un archivo. Funciona debajo de Android Nougat, pero en Android Nougat se bloquea. Solo se bloquea cuando bash abrir un archivo desde la tarjeta SD, no desde la partición del sistema. ¿Algún problema de permiso?

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 

Iniciar sesión:

android.os.FileUriExposedException: file: ///storage/emulated/0/test.txt expuesta más allá de la aplicación a través de Intent.getData ()

Editar:

Cuando se dirige a Android Nougat, file:// URI de file:// ya no están permitidos. Deberíamos usar content:// URI en su lugar. Sin embargo, mi aplicación necesita abrir archivos en directorios raíz. ¿Algunas ideas?

    Si su targetSdkVersion >= 24 , entonces tenemos que usar la clase FileProvider para dar acceso al archivo o carpeta particular para hacerlos accesibles para otras aplicaciones. Creamos nuestra propia clase heredando 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 el file:// URI con content:// URI:

    • Agregue una clase que FileProvider

       public class GenericFileProvider extends FileProvider {} 
    • Agregue una etiqueta FileProvider en AndroidManifest.xml debajo de la etiqueta. Especifique una autoridad única para el atributo android:authorities authors para evitar conflictos, las dependencias importadas pueden especificar ${applicationId}.provider y otras autoridades de uso común.

     < ?xml version="1.0" encoding="utf-8"?>      
    • A continuación, cree un archivo provider_paths.xml en la carpeta res/xml . Es posible que se necesite crear una carpeta si 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"?>    
    • El último paso es cambiar la línea de código a continuación en

       Uri photoURI = Uri.fromFile(createImageFile()); 

      a

       Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile()); 
    • Editar: si está intentando 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 se han explicado aquí.

    Además de la solución que utiliza 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 máquina virtual ignora la exposición URI 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.

    Me encontré con el problema de que si utilizo un content:// URI para enviar algo, algunas aplicaciones simplemente no pueden entenderlo. Y no está permitido degradar la versión del target SDK . En este caso, mi solución es útil.

    Actualizar:

    Como se menciona en el comentario, StrictMode es una herramienta de diagnóstico y no se supone que se use para este problema. Cuando publiqué esta respuesta hace un año, muchas aplicaciones solo pueden recibir archivos uris. Simplemente se bloquean cuando intenté enviarles un uri FileProvider. Esto ya está solucionado en la mayoría de las aplicaciones, así que deberíamos ir con la solución FileProvider.

    Si su aplicación se dirige a API 24+ y aún desea / necesita usar file: // intents, puede utilizar la forma 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 coja, sino que no quiere paralizarse mediante el uso de contenido: // bashs que muchas aplicaciones no entienden. Por ejemplo, abrir un archivo mp3 con contenido: // esquema ofrece muchas menos aplicaciones que cuando se abre con el esquema file: //. No quiero pagar las fallas de diseño de Google al limitar 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 las aplicaciones se hicieron para usar Archivos no como “contenido”, los archivos pueden ser editados y guardados, mientras que los archivos servidos sobre el esquema de contenido no pueden ( ¿ellos?).

    Si su targetSdkVersion es 24 o superior, no puede usar file: valores de file: Uri en Intents en los dispositivos Android 7.0+ .

    Tus elecciones son:

    1. targetSdkVersion su targetSdkVersion en 23 o menos, o

    2. Coloque su contenido en el almacenamiento interno, luego use FileProvider para que esté disponible de forma selectiva para 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 proyecto de muestra )

    Si targetSdkVersion es superior a 24 , FileProvider se utiliza para otorgar acceso.

    Cree un archivo xml (Ruta: res \ xml) provider_paths.xml

     < ?xml version="1.0" encoding="utf-8"?>    

    Agregue un proveedor en AndroidManifest.xml

         

    y reemplazar

     Uri uri = Uri.fromFile(fileImagePath); 

    a

     Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath); 

    y eres bueno para ir. Espero eso ayude.

    Primero necesita agregar un proveedor a su AndroidManifest

        ....      

    ahora crea un archivo en la carpeta de recursos xml (si usas el estudio Android, puedes presionar Alt + Enter luego de resaltar file_paths y seleccionar crear una opción de recurso xml)

    A continuación, en el archivo file_paths, ingrese

     < ?xml version="1.0" encoding="utf-8"?>     

    Este ejemplo es para la ruta externa que puede consultar aquí para obtener 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(); } 

    EDITAR : Agregué la carpeta raíz de la tarjeta sd en file_paths. He probado este código y funciona.

    @palash k answer es correcto y funcionó para archivos de almacenamiento internos, pero en mi caso también quiero abrir archivos de almacenamiento externo, mi aplicación se colgó cuando abrí archivos desde un almacenamiento externo como sdcard y usb, pero logré resolver el problema modificando provider_paths.xml de la respuesta aceptada

    cambie el provider_paths.xml como abajo

     < ?xml version="1.0" encoding="utf-8"?>     

    y en la clase de java (sin cambios como respuesta aceptada solo una pequeña edición)

     Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File) 

    Esto me ayuda a arreglar el locking de archivos de almacenamientos externos, espero que esto ayude a alguien que tenga el mismo problema que el mío 🙂

    Usar fileProvider es el camino a seguir. Pero puede usar esta solución alternativa simple:

    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")); 

    Usé la respuesta de Palash dada anteriormente, pero era algo incompleta, tuve que dar permiso como este

     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 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); 

    Solo pegue el siguiente código en la actividad onCreate ()

     StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); 

    Ignorará la exposición URI

    Para descargar el pdf del servidor, agregue el siguiente código en su clase de servicio. Espero que esto te ayude.

     File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName + ".pdf"); intent = new Intent(Intent.ACTION_VIEW); //Log.e("pathOpen", file.getPath()); Uri contentUri; contentUri = Uri.fromFile(file); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); if (Build.VERSION.SDK_INT >= 24) { Uri apkURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file); intent.setDataAndType(apkURI, "application/pdf"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } else { intent.setDataAndType(contentUri, "application/pdf"); } 

    Y sí, no olvide agregar permisos y proveedor en su manifiesto.

            

    https://stackoverflow.com/a/38858040/395097 esta respuesta está completa.

    Esta respuesta es para: ya tienes una aplicación que tenía como objective menos de 24, y ahora estás actualizando a targetSDKVersion> = 24.

    En Android N, solo se modifica el archivo uri expuesto a aplicaciones de terceros. (No de la manera en que lo estábamos usando antes). Cambie solo los lugares donde comparte el camino con la aplicación de terceros (cámara en mi caso)

    En nuestra aplicación, estábamos enviando uri a la aplicación de la cámara, en ese lugar esperamos que la aplicación de la cámara almacene la imagen capturada.

    1. Para Android N, generamos una nueva URL de contenido: // uri que apunta al archivo.
    2. Generamos la ruta habitual basada en la API de archivos para el mismo (utilizando un método anterior).

    Ahora tenemos 2 uri diferentes para el mismo archivo. # 1 se comparte con la aplicación de la cámara. Si la intención de la cámara es exitosa, podemos acceder a la imagen desde el n. ° 2.

    Espero que esto ayude.

    En mi caso, me deshice de la excepción al reemplazar SetDataAndType con solo SetData .