161 votes

Comment utiliser le support FileProvider pour partager du contenu avec d'autres applications ?

Je cherche un moyen de partager correctement (et non d'OUVRIR) un fichier interne avec une application externe en utilisant la bibliothèque de support Android. Fournisseur de fichiers .

En suivant l'exemple de la documentation,

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.example.android.supportv4.my_files"
    android:grantUriPermissions="true"
    android:exported="false">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/my_paths" />
</provider>

et utiliser ShareCompat pour partager un fichier avec d'autres applications comme suit :

ShareCompat.IntentBuilder.from(activity)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

ne fonctionne pas, car le FLAG_GRANT_READ_URI_PERMISSION n'accorde l'autorisation que pour l'Uri spécifié dans le champ data de l'intention, et non de la valeur de la EXTRA_STREAM supplémentaire (comme cela a été fixé par setStream ).

J'ai essayé de compromettre la sécurité en mettant android:exported a true pour le fournisseur, mais FileProvider vérifie en interne si elle est exportée. Si c'est le cas, elle lève une exception.

8 votes

+1 ceci semble être une question vraiment originale - il y a rien sur Google ou StackOverflow d'après ce que j'ai pu voir.

0 votes

Voici un post pour mettre en place une configuration de base de FileProvider en utilisant un projet d'exemple minimal sur Github : stackoverflow.com/a/48103567/2162226 . Il indique les étapes à suivre pour copier les fichiers (sans les modifier) dans un projet local autonome.

172voto

Lecho Points 2807

Utilisation de FileProvider à partir de la bibliothèque de support, vous devez manuellement accorder et révoquer les autorisations (au moment de l'exécution) pour que d'autres applications puissent lire des Uri spécifiques. Utilisez Context.grantUriPermission y Contexte.revokeUriPermission méthodes.

Par exemple :

//grant permision for app with package "packegeName", eg. before starting other app via intent
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

//revoke permisions
context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

En dernier recours, si vous ne pouvez pas fournir le nom du paquet, vous pouvez accorder l'autorisation à toutes les applications qui peuvent gérer une intention spécifique :

//grant permisions for all apps that can handle given intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
...
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

Méthode alternative selon le documentation :

  • Placez l'URI du contenu dans un Intent en appelant setData().
  • Ensuite, appelez la méthode Intent.setFlags() avec soit FLAG_GRANT_READ_URI_PERMISSION soit FLAG_GRANT_WRITE_URI_PERMISSION ou les deux.
  • Enfin, envoyez l'intention à une autre application. Le plus souvent, vous le faites en appelant setResult().

    Les autorisations accordées dans une intention restent en vigueur tant que la pile de l'activité réceptrice est active. Lorsque la pile se termine, le
    sont automatiquement supprimées. Les permissions accordées à un
    Les activités d'une application client sont automatiquement étendues aux autres applications de l'entreprise.
    composants de cette application.

Btw. si vous avez besoin, vous pouvez copier source de FileProvider et le changement attachInfo pour empêcher le fournisseur de vérifier s'il est exporté.

29 votes

Utilisation de la grantUriPermission nous devons fournir le nom du paquet de l'application à laquelle nous voulons accorder la permission. Cependant, lors d'un partage, nous ne savons généralement pas quelle application est la destination du partage.

0 votes

Que faire si l'on ne connaît pas le nom du paquet et que l'on veut simplement que les autres applications puissent l'ouvrir ?

0 votes

Dans ce cas, essayez d'utiliser intent.setData() et intent.setFlags (dernière partie de ma réponse après modification).

49voto

Divers Points 1524

Exemple de code entièrement fonctionnel permettant de partager un fichier à partir d'un dossier interne de l'application. Testé sur Android 7 et Android 5.

AndroidManifest.xml

</application>
   ....
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="android.getqardio.com.gmslocationtest"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
</application>

xml/provider_paths

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

Le code lui-même

    File imagePath = new File(getFilesDir(), "external_files");
    imagePath.mkdir();
    File imageFile = new File(imagePath.getPath(), "test.jpg");

    // Write data in your file

    Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile);

    Intent intent = ShareCompat.IntentBuilder.from(this)
                .setStream(uri) // uri from FileProvider
                .setType("text/html")
                .getIntent()
                .setAction(Intent.ACTION_VIEW) //Change if needed
                .setDataAndType(uri, "image/*")
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

   startActivity(intent);

3 votes

L'utilisation de la ShareCompat.IntentBuilder et l'autorisation de lecture de l'URI a finalement fonctionné pour moi.

0 votes

Pauses pour d'autres versions d'Android

0 votes

@OliverDixon : lesquels ? Je l'ai testé sur Android 6.0 et 9.0.

24voto

Oliver Kranz Points 3542

Cette solution fonctionne pour moi depuis OS 4.4. Pour qu'elle fonctionne sur tous les appareils, j'ai ajouté une solution de contournement pour les appareils plus anciens. Cela garantit que la solution la plus sûre est toujours utilisée.

Manifest.xml :

    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.package.name.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 :

<paths>
    <files-path name="app_directory" path="directory/"/>
</paths>

Java :

public static void sendFile(Context context) {
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType("text/plain");
    String dirpath = context.getFilesDir() + File.separator + "directory";
    File file = new File(dirpath + File.separator + "file.txt");
    Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
    intent.putExtra(Intent.EXTRA_STREAM, uri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    // Workaround for Android bug.
    // grantUriPermission also needed for KITKAT,
    // see https://code.google.com/p/android/issues/detail?id=76683
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            context.grantUriPermission(packageName, attachmentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }
    if (intent.resolveActivity(context.getPackageManager()) != null) {
        context.startActivity(intent);
    }
}

public static void revokeFileReadPermission(Context context) {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        String dirpath = context.getFilesDir() + File.separator + "directory";
        File file = new File(dirpath + File.separator + "file.txt");
        Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
        context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
}

L'autorisation est révoquée avec revokeFileReadPermission() dans les méthodes onResume et onDestroy() du fragment ou de l'activité.

1 votes

Cette solution a fonctionné pour moi mais seulement après avoir ajouté intent.setDataAndType(uri, "video/*") ; BTW la variable attachmentUri manquante est uri

0 votes

Voulez-vous dire que file ne changera pas de onResume a onDestroy ?

16voto

Luke Sleeman Points 395

Puisque, comme le dit Phil dans son commentaire sur la question originale, il s'agit d'un cas unique et qu'il n'y a pas d'autres informations sur le SO on dans google, j'ai pensé que je devais également partager mes résultats :

Dans mon application, FileProvider fonctionnait d'emblée pour partager des fichiers en utilisant l'intention de partage. Il n'y a pas eu de configuration spéciale ou de code nécessaire, en dehors de l'installation du FileProvider. Dans mon manifest.xml, j'ai placé :

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.my.apps.package.files"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/my_paths" />
    </provider>

Dans mon_paths.xml j'ai :

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files" path="." />
</paths>

Dans mon code, j'ai :

    Intent shareIntent = new Intent();
    shareIntent.setAction(Intent.ACTION_SEND);
    shareIntent.setType("application/xml");

    Uri uri = FileProvider.getUriForFile(this, "com.my.apps.package.files", fileToShare);
    shareIntent.putExtra(Intent.EXTRA_STREAM, uri);

    startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file)));

Et je suis capable de partager mes fichiers stockés dans le stockage privé de mes applications avec des applications telles que Gmail et google drive sans aucun problème.

11 votes

Malheureusement, cela ne fonctionne pas. La variable fileToShare ne fonctionne que si le fichier existe sur une carte SD ou un système de fichiers externe. L'intérêt d'utiliser FileProvider est de pouvoir partager le contenu du système interne.

0 votes

Cela semble fonctionner correctement avec les fichiers de stockage local (sur Android 5+). Mais j'ai des problèmes sur Android 4.

15voto

Rasmusob Points 36

D'après ce que je sais, cela ne fonctionne que sur les nouvelles versions d'Android. Vous devrez donc probablement trouver un autre moyen de procéder. Cette solution fonctionne pour moi sur la version 4.4, mais pas sur la 4.0 ou la 2.3.3. Ce n'est donc pas un moyen utile de partager du contenu pour une application qui est censée fonctionner sur n'importe quel appareil Android.

Dans manifest.xml :

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.mydomain.myapp.SharingActivity"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

Faites attention à la façon dont vous spécifiez les autorités. Vous devez spécifier l'activité à partir de laquelle vous allez créer l'URI et lancer l'intention de partage, dans ce cas l'activité est appelée SharingActivity. Cette exigence n'est pas évidente dans les documents de Google !

file_paths.xml :

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="just_a_name" path=""/>
</paths>

Faites attention à la façon dont vous spécifiez le chemin. Par défaut, le chemin indiqué ci-dessus est la racine de votre stockage interne privé.

Dans SharingActivity.java :

Uri contentUri = FileProvider.getUriForFile(getActivity(),
"com.mydomain.myapp.SharingActivity", myFile);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share with"));

Dans cet exemple, nous partageons une image JPEG.

Enfin, il est probablement bon de s'assurer que vous avez sauvegardé le fichier correctement et que vous pouvez y accéder avec quelque chose comme ceci :

File myFile = getActivity().getFileStreamPath("mySavedImage.jpeg");
if(myFile != null){
    Log.d(TAG, "File found, file description: "+myFile.toString());
}else{
    Log.w(TAG, "File not found!");
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X