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

738

L'application se bloque lorsque j'essaie d'ouvrir un fichier. Cela fonctionne en dessous d'Android Nougat, mais sur Android Nougat, il se bloque. Il ne se bloque que lorsque j'essaie d'ouvrir un fichier à partir de la carte SD, pas à partir de 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

Journal:

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

Éditer:

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

Thomas Vos
la source
20
J'ai l'impression que c'était une erreur qui rend la vie inutilement difficile pour les développeurs d'applications. Devoir regrouper un "FileProvider" et une "autorité" avec chaque application, ressemble à un passe-partout Enterprisey. Devoir ajouter un indicateur à chaque intention de fichier semble gênant et peut-être inutile. Il est désagréable de rompre l'élégant concept de «chemins». Et quel est l'avantage? Accorder sélectivement l'accès au stockage aux applications (alors que la plupart des applications ont un accès complet à la carte SD, en particulier celles qui fonctionnent sur les fichiers)?
nyanpasu64
2
essayez ceci, petit et parfait code stackoverflow.com/a/52695444/4997704
Binesh Kumar

Réponses:

1317

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

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

  • Ajouter une classe s'étendant FileProvider

    public class GenericFileProvider extends FileProvider {}
  • Ajoutez une <provider>balise FileProvider dansAndroidManifest.xml<application> balise under . Spécifiez une autorité unique pour l' android:authoritiesattribut afin d'éviter les conflits, les dépendances importées peuvent spécifier ${applicationId}.provideret 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}.provider"
            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 provider_paths.xmlfichier dansres/xml dossier. Il peut être nécessaire de créer un dossier s'il n'existe pas. Le contenu du fichier est indiqué ci-dessous. Il décrit que nous aimerions partager l'accès au stockage externe au 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() + ".provider", createImageFile());
  • Éditer: si vous utilisez l'intention d'ouvrir le fichier au système, vous devrez peut-être ajouter la ligne de code suivante:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

Veuillez vous référer, le code complet et la solution ont été expliqués ici.

Pkosta
la source
62
J'avais juste besoin d'ajouter intent.setFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
alorma
24
Cela fonctionnera-t-il pour toutes les versions d'Android, ou seulement à partir d'API 24?
développeur Android
9
cet article m'a aidé medium.com/@ali.muzaffar/…
AbdulMomen عبدالمؤمن
11
@rockhammer Je viens de tester cela avec Android 5.0, 6.0, 7.1 et 8.1, cela fonctionne dans tous les cas. La (Build.VERSION.SDK_INT > M)condition est donc inutile.
Sébastien
66
FileProviderne doit être étendu que si vous souhaitez remplacer l'un des comportements par défaut, sinon utilisez android:name="android.support.v4.content.FileProvider". Voir developer.android.com/reference/android/support/v4/content/…
JP Ventura
313

Outre la solution utilisant le FileProvider, il existe une autre façon de contourner ce problème . 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 fichierURI exposition au .

Méthode

builder.detectFileUriExposure()

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

J'ai rencontré un problème: si j'utilise un content:// URIpour envoyer quelque chose, certaines applications ne peuvent tout simplement pas le comprendre. Et déclasser letarget SDK version 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 ce problème. Lorsque j'ai posté cette réponse il y a un an, de nombreuses applications ne peuvent recevoir que des uris de fichier. Ils ont juste planté lorsque j'ai essayé de leur envoyer un uri FileProvider. Cela est maintenant corrigé dans la plupart des applications, nous devons donc opter pour la solution FileProvider.

hqzxzwb
la source
1
@LaurynasG De l'API 18 au 23, Android ne vérifie pas l'exposition par défaut à l'URI du fichier. L'appel de cette méthode active cette vérification. Depuis API 24, Android effectue cette vérification par défaut. Mais nous pouvons le désactiver en définissant un nouveau VmPolicy.
hqzxzwb
Y a-t-il une autre étape nécessaire pour que cela fonctionne? Ne fonctionne pas car il représente mon Moto G fonctionnant sous Android 7.0.
CKP78 du
3
Comment cela peut résoudre ce problème cependant, StrictMode est un outil de diagnostic qui devrait être activé en mode développeur et non en mode libération ???
Imene Noomene
1
@ImeneNoomene En fait, nous désactivons StrictMode ici. Il semble raisonnable que StrictMode ne soit pas activé en mode de publication, mais en fait, Android active certaines options StrictMode par défaut, quel que soit le mode de débogage ou le mode de publication. Mais d'une manière ou d'une autre, cette réponse ne devait être qu'une solution de contournement lorsque certaines applications cibles n'étaient pas préparées pour recevoir des uris de contenu. Maintenant que la plupart des applications ont ajouté la prise en charge des uris de contenu, nous devons utiliser le modèle FileProvider.
hqzxzwb
3
@ImeneNoomene Je suis totalement avec vous dans votre indignation. Vous avez raison, c'est un outil de diagnostic, ou du moins c'était il y a longtemps quand je l'ai ajouté à mes projets. C'est super frustrant! StrictMode.enableDefaults();, que j'exécute uniquement sur mes versions de développement, empêche cet incident de se produire - j'ai donc maintenant une application de production qui se bloque mais ne se bloque pas lors du développement. Donc, fondamentalement, l'activation d'un outil de diagnostic cache un problème grave. Merci @hqzxzwb de m'avoir aidé à démystifier cela.
Jon
174

Si targetSdkVersionest supérieur à 24 , 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>

Si vous utilisez androidx , le chemin d'accès FileProvider doit être:

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

et remplacer

Uri uri = Uri.fromFile(fileImagePath);

à

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

Modifier: lorsque vous incluez l'URI, Intentassurez-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 cela aide.

Pankaj Lilan
la source
2
@MaksimKniazev Pouvez-vous décrire votre erreur en bref? Pour que je puisse vous aider.
Pankaj Lilan
1
@PankajLilan, j'ai fait exactement ce que tu as dit. Mais chaque fois que j'ouvre mon pdf dans l'autre application, il apparaît vide (sa sauvegarde est correcte). Dois-je avoir besoin de modifier le xml? J'ai également ajouté la FLAG_GRANT_READ_URI_PERMISSION également;
Felipe Castilhos
1
Mon erreur, j'ajoutais la permission à la mauvaise intention. C'est la meilleure et la meilleure réponse la plus simple. Je vous remercie!
Felipe Castilhos
2
Il lève l'exception java.lang.IllegalArgumentException: impossible de trouver la racine configurée qui contient / le chemin d'accès à mon fichier est /storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf. Peux-tu aider s'il te plait ?
Jagdish Bhavsar
1
Je ne sais pas pourquoi mais j'ai dû ajouter à la fois les autorisations de lecture et d'écriture: target.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION) target.addFlags (Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
boîte
160

Si votre application cible l'API 24+ et que vous souhaitez / devez toujours utiliser file: // intents, vous pouvez utiliser une 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();
   }
}

La méthode StrictMode.disableDeathOnFileUriExposureest masquée et documentée 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 en utilisant du contenu: // des 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 son ouverture sur le schéma file: //. 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 schéma de contenu, mais le système n'est pas préparé pour cela, pendant des années, les applications ont été conçues pour utiliser des fichiers et non du «contenu», les fichiers peuvent être modifiés et enregistrés, tandis que les fichiers diffusés via le schéma de contenu ne peuvent pas l'être (peut ils?).

Pointeur nul
la source
3
"alors que les fichiers servis sur le schéma de contenu ne peuvent pas l'être (le peuvent-ils?)." - bien sûr, si vous avez un accès en écriture au contenu. ContentResolvera les deux openInputStream()et openOutputStream(). Une façon moins compliquée de procéder consiste simplement à configurer vous - même les règles de la machine virtuelle et à ne pas activer la file Urirègle.
CommonsWare
1
Exactement. Il est difficile de créer l'intégralité de votre application, puis découvrez après avoir ciblé 25 toutes les méthodes de votre caméra. Cela fonctionne pour moi jusqu'à ce que j'aie le temps de le faire de la bonne façon.
Matt W
5
Fonctionne sur Android 7. Merci
Anton Kizema
4
Fonctionne également sur Android 8, testé sur Huawei Nexus 6P.
Gonzalo Ledezma Torres
4
Je confirme que cela fonctionne sur la production (j'ai plus de 500 000 utilisateurs), actuellement la version 8.1 est la version la plus élevée, et elle fonctionne dessus.
Eli
90

Si vous avez targetSdkVersion24 ans ou plus, vous ne pouvez pas utiliser de file: Urivaleurs dans Intentssur les appareils Android 7.0+ .

Vos choix sont:

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

  2. Mettez votre contenu sur le stockage interne, puis utilisez-leFileProvider pour le rendre sélectivement disponible pour 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);

(à partir de cet exemple de projet )

CommonsWare
la source
Merci d'avoir répondu. Que se passe-t-il lorsque j'utilise ceci avec des fichiers sur la /systempartition? Chaque application devrait pouvoir accéder à cette partition sans root.
Thomas Vos
2
@SuperThomasLab: Je ne compterais pas sur tout pour /systemêtre lisible dans le monde. Cela étant dit, je suppose que vous obtiendrez toujours cette exception. Je soupçonne qu'ils vérifient simplement le schéma et n'essaient pas de déterminer si le fichier est vraiment lisible dans le monde. Cependant, FileProvidercela ne vous aidera pas, car vous ne pouvez pas lui apprendre à servir /system. Vous pouvez créer une stratégie personnalisée pour moiStreamProvider ou lancer la vôtre ContentProviderpour surmonter le problème.
CommonsWare
Je pense toujours à la façon dont je vais résoudre ce problème. L'application que je mets à jour avec le support d'Android N est un navigateur racine. Mais maintenant, vous ne pouvez plus ouvrir aucun fichier dans les répertoires racine. ( /data, /system), à cause de ce "bon changement".
Thomas Vos
1
quels sont les inconvénients les plus importants à la suppression de targetSdkVersion à 23? thnx
rommex
2
@rommex: Je ne sais pas ce qui est qualifié de "plus important". Par exemple, les utilisateurs qui travaillent en mode écran partagé ou sur des appareils multi-fenêtres de forme libre (Chromebooks, Samsung DeX) seront informés que votre application peut ne pas fonctionner avec plusieurs fenêtres. Que cela soit important ou non dépend de vous.
CommonsWare
47

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 mis en surbrillance file_paths et sélectionnez créer 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 concerne le chemin externe que vous pouvez consulter ici pour plus d'options. Cela vous permettra de partager des fichiers qui se trouvent dans ce dossier et son sous-dossier.

Il ne reste plus qu'à 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 le file_paths. J'ai testé ce code et cela fonctionne.

Karn Patel
la source
1
Merci pour ça. Je veux également vous faire savoir qu'il existe un meilleur moyen d'obtenir l'extension de fichier. String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()); En outre, je recommande à quiconque cherche des réponses de lire d' abord FileProvider et de comprendre ce que vous traitez ici avec les autorisations de fichiers dans Android N et au-dessus. Il existe des options pour le stockage interne par rapport au stockage externe et également pour les chemins de fichiers standard contre les chemins de cache.
praneetloke
2
J'obtenais l'exception suivante: java.lang.IllegalArgumentException: Failed to find configured root ...et la seule chose qui a fonctionné était <files-path path="." name="files_root" />sur le fichier xml au lieu de <external-path .... Mon fichier a été enregistré dans la mémoire interne.
steliosf
26

La réponse @palash k est correcte et a fonctionné 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 en classe java (pas de changement comme réponse acceptée juste une petite modification)

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

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

Ramz
la source
1
Où avez-vous trouvé <root-paths'il vous plaît? Ça marche. <external-path path="Android/data/${applicationId}/" name="files_root" />n'a eu aucun effet pour les fichiers ouverts à partir du stockage externe.
t0m
je trouve cela à partir de divers résultats de recherche, laissez-moi vérifier à nouveau et revenir à u dès que possible
Ramz
aussi le stockage externe que vous mentionnez est une carte SD ou un stockage intégré?
Ramz
Désolé pour l'inexactitude. Je voulais dire Android/data/${applicationId}/dans SDcard.
t0m
1
Besoin d'ajouter ceci à l'intention: intent.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
s-hunter
26

Ma solution était de «Uri.parse» le chemin du fichier en tant que 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, doesn't work in Android 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

Il semble que fromFile () utilise un pointeur de fichier, qui, je suppose, pourrait être peu sûr lorsque les adresses mémoire sont exposées à toutes les applications. Mais une chaîne de chemin de fichier n'a jamais blessé personne, donc cela fonctionne sans lancer FileUriExposedException.

Testé sur les niveaux d'API 9 à 27! Ouvre correctement le fichier texte pour le modifier dans une autre application. Ne nécessite pas FileProvider, ni la bibliothèque de support Android.

CrazyJ36
la source
J'aimerais avoir vu ça en premier. Je n'ai pas prouvé que cela fonctionnait pour moi, mais c'est beaucoup moins lourd que FileProvider.
Dale
Une note sur la raison pour laquelle cela fonctionne réellement: ce n'est pas le pointeur de fichier qui pose le problème, mais le fait que l'exception ne se produit que si vous avez un chemin avec 'file: //', qui est automatiquement ajouté avec fromFile, mais pas avec parse .
Xmister
3
Cela ne fait pas exception, mais il ne peut pas non plus envoyer le fichier à l'application associée. Donc, ça n'a pas marché pour moi.
Serdar Samancıoğlu
1
Cela échouera sur Android 10 et supérieur, car vous ne pouvez pas supposer que l'autre application a accès au stockage externe via le système de fichiers.
CommonsWare
24

Collez simplement le code ci-dessous dans l'activité onCreate ()

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

Il ignorera l'exposition URI

Kaushal Sachan
la source
1
c'est l'une des solutions mais pas la solution standard. Stil les personnes qui ont dévalorisé les réponses ont tort car le code fonctionne également avec la solution de travail.
saksham
23

Collez simplement le code ci-dessous dans l'activité onCreate().

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

Il ignorera l'exposition URI.

Bon codage :-)

Ripdaman Singh
la source
1
Quels sont les inconvénients de cela?
James F
1
Cela échouera sur Android 10 et supérieur, car vous ne pouvez pas supposer que l'autre application a accès au stockage externe via le système de fichiers.
CommonsWare
18

L'utilisation de fileProvider est la solution. Mais vous pouvez utiliser cette solution de contournement simple:

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

remplacer:

startActivity(intent);

par

startActivity(Intent.createChooser(intent, "Your title"));
Simon
la source
7
Le sélecteur sera bientôt corrigé par Google pour contenir la même vérification. Ce n'est pas une solution.
Pointer Null
Celui-ci fonctionne mais ne fonctionnera pas dans les futures versions d'Android.
Diljeet
13

J'ai utilisé la réponse de Palash donnée ci-dessus mais elle était quelque peu incomplète, j'ai dû fournir 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);
Max
la source
11

Collez simplement le code ci-dessous dans l'activité onCreate ()

Générateur StrictMode.VmPolicy.Builder = nouveau StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy (builder.build ());

Il ignorera l'exposition URI

AnilS
la source
Cela supprimera les politiques strictmode. et ignorera l'avertissement de sécurité. Pas une bonne solution.
inspire_coding
Il échouera également sur Android 10 et supérieur, car vous ne pouvez pas supposer que l'autre application a accès au stockage externe via le système de fichiers.
CommonsWare
7

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..."));
DEVSHK
la source
Cela échouera sur Android 10 et supérieur, car vous ne pouvez pas supposer que l'autre application a accès au stockage externe via le système de fichiers.
CommonsWare
7

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 de créer

public class GenericFileProvider extends FileProvider {}

Je teste sur Android 5.0, 6.0 et Android 9.0 et c'est un succès.

Alex
la source
J'ai testé cette solution et elle fonctionne très bien avec une petite modification: intention.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.putExtra (Intent.EXTRA_STREAM, myPhotoFileUri) intention.type = "image / png" startActivity (Intent.createChooser (intention, " Partager l'image via ")) Cela fonctionne fin sur Android 7 et 8.
inspire_coding
4

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>
Bhoomika Chauhan
la source
1
c'est quoi @xml/provider_paths?
adityasnl
1
@Heisenberg s'il vous plaît se référer à Rahul Upadhyay post de l'url: stackoverflow.com/questions/38555301/…
Bhoomika Chauhan
3

Je ne sais pas pourquoi, j'ai fait tout exactement la même chose que Pkosta ( https://stackoverflow.com/a/38858040 ) mais j'ai continué à recevoir 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était en train de définir getIntent().addFlagsau lieu de fonctionner sur mon playIntent nouvellement déclaré.

nyanpasu64
la source
2

J'ai mis cette méthode pour que le chemin imageuri pénètre facilement 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);
}
Jitu Batiya
la source
2
As of Android N, in order to work around this issue, you need to use the FileProvider API

Il y a 3 étapes principales ici comme mentionné ci-dessous

Étape 1: entrée du manifeste

<manifest ...>
    <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>
</manifest>

Étape 2: créer un fichier XML 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>

Étape 3: changements de code

File file = ...;
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
// Old Approach
    install.setDataAndType(Uri.fromFile(file), mimeType);
// End Old approach
// New Approach
    Uri apkURI = FileProvider.getUriForFile(
                             context, 
                             context.getApplicationContext()
                             .getPackageName() + ".provider", file);
    install.setDataAndType(apkURI, mimeType);
    install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// End New Approach
    context.startActivity(install);
Suraj Bahadur
la source
1

Je sais que c'est une question 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: pour afficher votre image à partir de votre chemin dans 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 comme pdf et dans mon cas, cela a très bien fonctionné


la source
0

J'ai passé presque une journée à essayer de comprendre pourquoi je recevais cette exception. Après beaucoup de lutte, cette configuration a parfaitement fonctionné ( Kotlin ):

AndroidManifest.xml

<provider
  android:name="androidx.core.content.FileProvider"
  android:authorities="com.lomza.moviesroom.fileprovider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_paths" />
</provider>

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <files-path name="movies_csv_files" path="."/>
</paths>

Intention elle-même

fun goToFileIntent(context: Context, file: File): Intent {
    val intent = Intent(Intent.ACTION_VIEW)
    val contentUri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
    val mimeType = context.contentResolver.getType(contentUri)
    intent.setDataAndType(contentUri, mimeType)
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION

    return intent
}

J'explique tout le processus ici .

lomza
la source
-1

https://stackoverflow.com/a/38858040/395097 cette réponse est complète.

Cette réponse est pour - vous avez déjà une application qui ciblait en dessous de 24, et maintenant vous passez à targetSDKVersion> = 24.

Dans Android N, seul l'uri du fichier exposé à une application tierce est modifié. (Pas la façon dont nous l'utilisions auparavant). Modifiez donc uniquement les endroits où vous partagez le chemin avec une application tierce (appareil photo dans mon cas)

Dans notre application, nous envoyions l'URI à l'application Appareil photo, à cet endroit, nous nous attendons à ce que l'application Appareil photo stocke l'image capturée.

  1. Pour Android N, nous générons un nouveau contenu: // URL basée sur uri pointant vers un fichier.
  2. Nous générons le chemin basé sur l'API de fichier habituel pour le même (en utilisant une méthode plus ancienne).

Maintenant, nous avons 2 uri différents pour le même fichier. # 1 est partagé avec l'application Appareil photo. Si l'objectif de la caméra est réussi, nous pouvons accéder à l'image à partir du # 2.

J'espère que cela t'aides.

Aram
la source
1
Vous faites référence à une réponse déjà publiée ici, si vous devez la compléter, commentez la réponse plz.
IgniteCoders
1
@IgniteCoders Comme je l'ai clairement mentionné dans le message, ma réponse couvre le cas d'utilisation associé.
Aram
-1

Xamarin.Android

Remarque: Le chemin xml / provider_paths.xml (.axml) n'a pas pu être résolu, même après avoir créé le dossier xml sous Resources (il peut peut-être être placé dans un emplacement existant comme Values , n'a pas essayé), j'ai donc recours à ce qui fonctionne pour l'instant. Les tests ont montré qu'il ne doit être appelé qu'une fois par exécution d'application (ce qui est logique étant qu'il modifie l'état de fonctionnement de la machine virtuelle hôte).

Remarque: xml doit être en majuscule, donc Resources / Xml / provider_paths.xml

Java.Lang.ClassLoader cl = _this.Context.ClassLoader;
Java.Lang.Class strictMode = cl.LoadClass("android.os.StrictMode");                
System.IntPtr ptrStrictMode = JNIEnv.FindClass("android/os/StrictMode");
var method = JNIEnv.GetStaticMethodID(ptrStrictMode, "disableDeathOnFileUriExposure", "()V");                
JNIEnv.CallStaticVoidMethod(strictMode.Handle, method);
Sam est
la source
-1

La réponse de @Pkosta est une façon de procéder.

Outre l'utilisation FileProvider, vous pouvez également insérer le fichier MediaStore(en particulier pour les fichiers image et vidéo), car les fichiers de MediaStore sont accessibles à toutes les applications:

Le MediaStore est principalement destiné aux types MIME vidéo, audio et image, mais à partir d'Android 3.0 (API niveau 11), il peut également stocker des types non multimédias (voir MediaStore.Files pour plus d'informations). Les fichiers peuvent être insérés dans le MediaStore à l'aide de scanFile (), après quoi un Uri content: // style approprié pour le partage est transmis au rappel onScanCompleted () fourni. Notez qu'une fois ajouté au système MediaStore, le contenu est accessible à n'importe quelle application sur l'appareil.

Par exemple, vous pouvez insérer un fichier vidéo dans MediaStore comme ceci:

ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, videoFilePath);
Uri contentUri = context.getContentResolver().insert(
      MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);

contentUriest comme content://media/external/video/media/183473, qui peut être passé directement à Intent.putExtra:

intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);

Cela fonctionne pour moi et évite les tracas de l'utilisation FileProvider.

NeoWang
la source
-1

Laissez-le simplement ignorer l'exposition URI ... Ajoutez-le après la création

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); 
Anurag Bhalekar
la source
Cela échouera sur Android 10 et supérieur, car vous ne pouvez pas supposer que l'autre application a accès au stockage externe via le système de fichiers.
CommonsWare
Cela ne doit pas être utilisé dans une application de production.
Jorgesys
-1

Essayez cette solution

METTEZ CES AUTORISATIONS DANS LE MANIFESTE

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.CAMERA" />

INTENTION DE CAPTURER L'IMAGE

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
                }

OBTENEZ UNE IMAGE CAPTURÉE DANS ONACTIVITYRESULT

@Override
            protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                super.onActivityResult(requestCode, resultCode, data);
                if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
                    Bundle extras = data.getExtras();
                    Bitmap imageBitmap = (Bitmap) extras.get("data");
                    // CALL THIS METHOD TO GET THE URI FROM THE BITMAP
                    Uri tempUri = getImageUri(getApplicationContext(), imageBitmap);
                    //DO SOMETHING WITH URI
                }
            } 

PROCÉDÉ POUR OBTENIR UN URI D'IMAGE

public Uri getImageUri(Context inContext, Bitmap inImage) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
        String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
        return Uri.parse(path);
    }
Abdul Basit Rishi
la source
quelqu'un peut-il me dire pourquoi voter contre. c'est une solution 100% fonctionnelle.
Abdul Basit Rishi
Cela vous donne uniquement la vignette et non l'image complète.
Build3r
-2

Dans mon cas, je me suis débarrassé de l'exception en remplaçant SetDataAndTypepar juste SetData.

thomiel
la source