web-dev-qa-db-fra.com

android.os.FileUriExposedException: fichier: ///storage/emulated/0/test.txt exposé au-delà de l'application via Intent.getData ()

L'application se bloque lorsque j'essaie d'ouvrir un fichier. Il fonctionne sous Android Nougat, mais sur Android Nougat, il se bloque. Il se bloque uniquement lorsque j'essaie d'ouvrir un fichier depuis la carte SD, et non depuis la partition système. Un problème de permission?

Exemple de code:

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

Bûche:

Android.os.FileUriExposedException: fichier: ///storage/emulated/0/test.txt exposé au-delà de l'application via Intent.getData ()

Edit:

Lorsque vous ciblez Android Nougat, file://, les URI ne sont plus autorisés. Nous devrions plutôt utiliser content:// URI. Cependant, mon application doit ouvrir des fichiers dans les répertoires racine. Des idées?

650
Thomas Vos

Si votre targetSdkVersion >= 24, nous devons utiliser la classe FileProvider pour donner accès à un fichier ou à un dossier particulier afin de les rendre accessibles à d'autres applications. Nous créons notre propre classe héritant de FileProvider afin de nous assurer que notre fournisseur de fichiers FileProvider n'entre pas en conflit avec les fournisseurs de fichiers FileProviders déclarés dans les dépendances importées comme décrit ici .

Étapes pour remplacer file:// URI par content:// URI:

  • Ajouter une classe d'extension FileProvider

    public class GenericFileProvider extends FileProvider {}
    
  • Ajoutez une balise FileProvider <provider> dans la balise AndroidManifest.xml sous la balise <application>. Spécifiez une autorité unique pour l'attribut Android:authorities afin d'éviter les conflits. Les dépendances importées peuvent spécifier ${applicationId}.provider et d'autres autorités couramment utilisées.

<?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}.fileprovider"
            Android:exported="false"
            Android:grantUriPermissions="true">
            <meta-data
                Android:name="Android.support.FILE_PROVIDER_PATHS"
                Android:resource="@xml/provider_paths"/>
        </provider>
    </application>
</manifest>
  • Créez ensuite un fichier provider_paths.xml dans le dossier res/xml. Un dossier peut être nécessaire pour créer s'il n'existe pas. Le contenu du fichier est présenté ci-dessous. Il indique que nous souhaitons partager l'accès au stockage externe dans le dossier racine (path=".") avec le nom 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>
  • La dernière étape consiste à modifier la ligne de code ci-dessous dans

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

    à

    Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
    
  • Edit: Si vous utilisez l'intention de faire en sorte que le système ouvre votre fichier, vous devrez peut-être ajouter la ligne de code suivante:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

S'il vous plaît se référer, le code complet et la solution a été expliquée ici.

1154
Pkosta

Outre la solution utilisant la variable FileProvider, il existe un autre moyen de contourner ce problème. Tout simplement

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

dans Application.onCreate(). De cette manière, la VM ignore le fichier URI exposition.

Méthode

builder.detectFileUriExposure()

active la vérification de l'exposition du fichier, qui est également le comportement par défaut si nous ne configurons pas VmPolicy.

J'ai rencontré un problème qui signifie que si j'utilise un content://URI pour envoyer quelque chose, certaines applications ne peuvent tout simplement pas le comprendre. Et déclasser la version target SDK n'est pas autorisé. Dans ce cas, ma solution est utile.

Mise à jour:

Comme mentionné dans le commentaire, StrictMode est un outil de diagnostic et n'est pas censé être utilisé pour résoudre ce problème. Lorsque j'ai posté cette réponse il y a un an, de nombreuses applications ne peuvent recevoir que des fichiers uris. Ils se bloquent juste quand j'ai essayé de leur envoyer un FileProvider URI. Ceci est corrigé dans la plupart des applications, nous devrions donc utiliser la solution FileProvider.

285
hqzxzwb

Si votre application cible l'API 24+ et que vous souhaitez/devez toujours utiliser file: // intents, vous pouvez utiliser la méthode hacky pour désactiver le contrôle à l'exécution:

if(Build.VERSION.SDK_INT>=24){
   try{
      Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
      m.invoke(null);
   }catch(Exception e){
      e.printStackTrace();
   }
}

La méthode StrictMode.disableDeathOnFileUriExposure est masquée et documentée sous la forme:

/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/

Le problème est que mon application n'est pas boiteuse, mais ne veut pas être handicapée par l'utilisation de content: // intentions qui ne sont pas comprises par de nombreuses applications. Par exemple, l’ouverture d’un fichier mp3 avec le schéma content: // offre beaucoup moins d’applications que lors de l’ouverture du même schéma sur fichier: //. Je ne veux pas payer pour les défauts de conception de Google en limitant les fonctionnalités de mon application.

Google souhaite que les développeurs utilisent le modèle de contenu, mais le système n'est pas préparé à cela. Pendant des années, les applications ont été conçues pour utiliser des fichiers non "contenu", les fichiers peuvent être modifiés et sauvegardés, alors que les fichiers servis sur le modèle de contenu ne peuvent pas l'être. ils?).

148
Pointer Null

Si targetSdkVersion est supérieur à 24 , alors FileProvider est utilisé pour accorder l'accès.

Créez un fichier xml (chemin: res\xml) provider_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>


Ajouter un fournisseur dans 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 vous utilisez androidx , le chemin d'accès FileProvider doit être:

 Android:name="androidx.core.content.FileProvider"

et remplace

Uri uri = Uri.fromFile(fileImagePath);

à

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

Edit: Pendant que vous incluez l'URI avec un Intent assurez-vous d'ajouter la ligne ci-dessous:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

et vous êtes prêt à partir. J'espère que ça aide.

141
Pankaj Lilan

Si votre targetSdkVersion est égal ou supérieur à 24, vous ne pouvez pas utiliser file:Uri valeurs dans Intents sur Android 7.0+ appareils =.

Vos choix sont:

  1. Laissez tomber votre targetSdkVersion à 23 ou moins, ou

  2. Mettez votre contenu en mémoire interne, puis tilisez FileProvider pour le rendre disponible de manière sélective pour les autres applications

Par exemple:

Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));

i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);

(de cet exemple de projet )

85
CommonsWare

Vous devez d’abord ajouter un fournisseur à votre manifeste Android.

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

créez maintenant un fichier dans le dossier des ressources xml (si vous utilisez Android studio, vous pouvez appuyer sur Alt + Entrée après avoir mis en évidence chemin_fichiers et sélectionner l'option de création d'une ressource xml)

Suivant dans le fichier chemin_fichiers entrez

<?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>

Cet exemple est pour external-path, vous pouvez vous référer à ici pour plus d'options. Cela vous permettra de partager des fichiers qui se trouvent dans ce dossier et son sous-dossier.

Maintenant, tout ce qui reste à faire est de créer l'intention comme suit:

    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: J'ai ajouté le dossier racine de la carte SD dans les chemins_fichiers. J'ai testé ce code et cela fonctionne.

46
Karn Patel

@palash k answer est correct et fonctionne pour les fichiers de stockage interne, mais dans mon cas, je souhaite également ouvrir des fichiers à partir d'un stockage externe. Mon application s'est bloquée lors de l'ouverture d'un fichier à partir d'un stockage externe, comme sdcard et usb, mais j'ai réussi à résoudre le problème en modifiant provider_paths.xml à partir de la réponse acceptée

changer le provider_paths.xml comme ci-dessous

<?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>

et dans la classe Java (pas de modification car la réponse acceptée est juste une petite modification)

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

Cela m'aide à résoudre le problème des fichiers stockés dans des mémoires externes. J'espère que cela aidera quelqu'un qui a le même problème que le mien :)

25
Ramz

Il suffit de coller le code ci-dessous dans l’activité onCreate ()

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

Il ignorera l'exposition à l'URI

21
Kaushal Sachan

Ma solution consistait à "Uri.parse" le chemin du fichier sous forme de chaîne, au lieu d'utiliser Uri.fromFile ().

String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt";
File file = new File(storage);
Uri uri;
if (Build.VERSION.SDK_INT < 24) {
    uri = Uri.fromFile(file);
} else {
    uri = Uri.parse(file.getPath()); // My work-around for new SDKs, causes ActivityNotFoundException in API 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

Il semblerait que fromFile () utilise un pointeur de fichier, ce qui, je suppose, pourrait ne pas être sûr lorsque les adresses de mémoire sont exposées à toutes les applications. Mais un chemin de fichier String ne fait de mal à personne, il fonctionne donc sans exception FileUriExposedException.

Testé aux niveaux 9 à 27 de l'API! Ouvre avec succès le fichier texte pour le modifier dans une autre application. Ne nécessite pas FileProvider, ni la bibliothèque de support Android.

19
CrazyJ36

Utiliser FileProvider est la voie à suivre. Mais vous pouvez utiliser cette solution de contournement simple:

WARNING: il sera corrigé dans la prochaine Android version - https://issuetracker.google.com/issues/37122890 # comment4

remplacer:

startActivity(intent);

par

startActivity(Intent.createChooser(intent, "Your title"));
18
Simon

Il suffit de coller le code ci-dessous dans l'activité onCreate().

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

Il ignorera l'exposition à l'URI.

Bonne codage :-)

18
Ripdaman Singh

J'ai utilisé la réponse de Palash donnée ci-dessus, mais elle était quelque peu incomplète, je devais donner une autorisation comme celle-ci.

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

Il suffit de coller le code ci-dessous dans l’activité onCreate ()

Constructeur StrictMode.VmPolicy.Builder = nouveau StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy (builder.build ());

Il ignorera l'exposition à l'URI

6
AnilS

ajoutez ces deux lignes dans onCreate

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

Méthode de partage

File dir = new File(Environment.getExternalStorageDirectory(), "ColorStory");
File imgFile = new File(dir, "0.png");
Intent sendIntent = new Intent(Intent.ACTION_VIEW);
sendIntent.setType("image/*");
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + imgFile));
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(sendIntent, "Share images..."));
5
DEVSHK

Pour télécharger le pdf depuis le serveur, ajoutez le code ci-dessous dans votre classe de service. J'espère que cela vous sera utile.

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

Et oui, n'oubliez pas d'ajouter des autorisations et un fournisseur dans votre manifeste.

<uses-permission Android:name="Android.permission.INTERNET" />
<uses-permission Android:name="Android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission Android:name="Android.permission.READ_EXTERNAL_STORAGE" />

<application

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

</application>
4
Bhoomika Chauhan

Je ne sais pas pourquoi, j'ai tout fait exactement de la même façon que Pkosta ( https://stackoverflow.com/a/3885804 ), mais j'ai continué à avoir des erreurs:

Java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted

J'ai perdu des heures sur cette question. Le coupable? Kotlin.

val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

intent définissait en fait getIntent().addFlags au lieu de fonctionner sur mon playIntent nouvellement déclaré.

3
jimbo1qaz

je mets cette méthode afin chemin imageuri facilement entrer dans le contenu.

enter code here
public Uri getImageUri(Context context, Bitmap inImage)
{
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes);
    String path = MediaStore.Images.Media.insertImage(context.getContentResolver(), 
    inImage, "Title", null);
    return Uri.parse(path);
}
2
Jitu Batiya

Je sais que la question est assez ancienne mais cette réponse s'adresse aux futurs téléspectateurs. J'ai donc rencontré un problème similaire et, après des recherches, j'ai trouvé une alternative à cette approche.

Votre intention ici, par exemple: Voir votre image depuis votre chemin à Kotlin

 val intent = Intent()
 intent.setAction(Intent.ACTION_VIEW)
 val file = File(currentUri)
 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
 val contentURI = getContentUri(context!!, file.absolutePath)
 intent.setDataAndType(contentURI,"image/*")
 startActivity(intent)

Fonction principale ci-dessous

private fun getContentUri(context:Context, absPath:String):Uri? {
        val cursor = context.getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            arrayOf<String>(MediaStore.Images.Media._ID),
            MediaStore.Images.Media.DATA + "=? ",
            arrayOf<String>(absPath), null)
        if (cursor != null && cursor.moveToFirst())
        {
            val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
            return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id))
        }
        else if (!absPath.isEmpty())
        {
            val values = ContentValues()
            values.put(MediaStore.Images.Media.DATA, absPath)
            return context.getContentResolver().insert(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        }
        else
        {
            return null
        }
    }

De même, au lieu d’une image, vous pouvez utiliser n’importe quel autre format de fichier tel que pdf et dans mon cas, cela a très bien fonctionné.

1
S.Sho

Voici ma solution:

dans Manifest.xml

<application
            Android:name=".main.MainApp"
            Android:allowBackup="true"
            Android:icon="@drawable/ic_app"
            Android:label="@string/application_name"
            Android:logo="@drawable/ic_app_logo"
            Android:theme="@style/MainAppBaseTheme">

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

dans res/xml/provider_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>

dans mon fragment, j'ai le code suivant:

 Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);               
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);

C'est tout ce dont vous avez besoin.

Aussi pas besoin pour créer

public class GenericFileProvider extends FileProvider {}

Je teste sur Android 5.0, 6.0 et Android 9.0 et sa réussite fonctionne.

1
Alex