Question 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 à partir de la carte SD, pas à partir de la partition système. Un problème d'autorisation?

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:   file: ///storage/emulated/0/test.txt exposée au-delà de l'application via   Intent.getData ()

Modifier:

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


436
2017-07-05 09:51


origine


Réponses:


Si ton targetSdkVersion >= 24, alors nous devons utiliser FileProvider classe pour donner accès au fichier ou dossier particulier pour les rendre accessibles aux autres applications. Nous créons notre propre héritage de classe FileProvider afin de s'assurer que FileProvider ne soit pas en conflit avec FileProviders déclaré dans les dépendances importées comme décrit ici.

Étapes à suivre pour remplacer file:// URI avec content:// URI:

  • Ajouter une classe FileProvider

    public class GenericFileProvider extends FileProvider {}
    
  • Ajoutez une balise FileProvider dans AndroidManifest.xml sous l'étiquette. Spécifiez une autorité unique pour le android:authorities attribut pour é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}.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>
  • Ensuite, créez un provider_paths.xml déposer res/xml dossier. Un dossier peut être nécessaire pour créer s'il n'existe pas. Le contenu du fichier est illustré ci-dessous. Il décrit que nous aimerions partager l’accès au stockage externe dans le dossier racine. (path=".") avec le nom fichiers_externe.
<?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 à changer la ligne de code ci-dessous

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

    à

    Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
    
  • Modifier: Si vous utilisez une intention pour 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é ici.


831
2017-08-09 18:33



Outre la solution utilisant le FileProvider, il y a une autre façon de contourner cela. Tout simplement

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

dans Application.onCreate(). De cette façon, la machine virtuelle ignore le fichier URI exposition.

Méthode

builder.detectFileUriExposure()

active le contrôle d'exposition des fichiers, qui est également le comportement par défaut si nous ne configurons pas VmPolicy.

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

Mettre à jour:

Comme mentionné dans le commentaire, StrictMode est un outil de diagnostic, et n'est pas censé être utilisé pour 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 tombent juste quand j'ai essayé d'envoyer un uri FileProvider à eux. Ceci est corrigé dans la plupart des applications maintenant, donc nous devrions aller avec la solution FileProvider.


215
2017-11-18 10:31



Si votre application cible l'API 24+ et que vous souhaitez toujours utiliser / deviez utiliser file: // intents, vous pouvez utiliser la méthode hacky pour désactiver la vérification de l'exécution:

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

Méthode StrictMode.disableDeathOnFileUriExposure est caché et documenté comme:

/**
* 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 paralysé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 un système content: // offre beaucoup moins d'applications que lors de l'ouverture d'un même schéma over::. Je ne veux pas payer pour les défauts de conception de Google en limitant les fonctionnalités de mon application.

Google veut que les développeurs utilisent le schéma 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 "content", les fichiers peuvent être édités et sauvegardés, ils?).


110
2018-02-24 11:21



Si ton targetSdkVersion est de 24 ou plus, vous ne pouvez pas utiliser file:  Uri valeurs dans Intents sur les appareils Android 7.0 et versions ultérieures.

Vos choix sont les suivants:

  1. Déposez votre targetSdkVersion à 23 ou moins, ou

  2. Mettez votre contenu sur le stockage interne, puis utilisation FileProvider pour le rendre disponible de manière sélective à d'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 ce projet d'exemple)


73
2017-07-05 12:42



Si targetSdkVersion est plus élevé que 24, puis FileProvider est utilisé pour accorder l'accès.

Créer 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>

et remplacer 

Uri uri = Uri.fromFile(fileImagePath);

à

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

et vous êtes bon à faire. J'espère que cela aide.


68
2017-08-18 07:58



Vous devez d'abord ajouter un fournisseur à votre 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>

Créez maintenant un fichier dans le dossier de ressources XML (si vous utilisez Android Studio, vous pouvez appuyer sur Alt + Entrée après avoir sélectionné chemin_fichiers et sélectionner une option de ressource XML)

Ensuite, dans le fichier file_paths, 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 refere ici pour plus d'options. Cela vous permettra de partager les fichiers qui se trouvent dans ce dossier et son sous-dossier.

Maintenant tout ce qui reste 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();
    }

MODIFIER: J'ai ajouté le dossier racine de la carte SD dans les chemins_fichiers. J'ai testé ce code et cela fonctionne.


40
2017-08-12 19:45



@palash k answer est correct et a fonctionné pour les fichiers de stockage internes, mais dans mon cas, je veux aussi ouvrir des fichiers depuis un stockage externe, mon application s'est bloquée en ouvrant un fichier de stockage externe comme sdcard et usb provider_paths.xml de la réponse acceptée

changer la 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 en Java classe (Pas de changement comme la réponse acceptée juste une petite modification)

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

Cela m'aide à résoudre le crash des fichiers provenant de stockages externes, en espérant que cela aidera quelqu'un ayant le même problème que le mien :)


24
2018-01-15 11:59



Utiliser le fileProvider est le chemin à parcourir. Mais vous pouvez utiliser cette solution de contournement simple:

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

remplacer:

startActivity(intent);

par

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

15
2018-02-08 03:02



J'ai utilisé la réponse de Palash donnée ci-dessus mais elle était quelque peu incomplète, je devais fournir une permission 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);

10
2017-12-11 16:05