Come utilizzare il supporto FileProvider per condividere contenuti con altre app?


142

Sto cercando un modo per condividere correttamente (non APERTO) un file interno con un'applicazione esterna utilizzando FileProvider della libreria di supporto Android .

Seguendo l'esempio sui documenti,

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

e usando ShareCompat per condividere un file con altre app come segue:

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

non funziona, poiché FLAG_GRANT_READ_URI_PERMISSION concede solo l'autorizzazione per l'URI specificato sull'intento data, non il valore EXTRA_STREAMdell'extra (come impostato da setStream).

Ho provato a compromettere la sicurezza impostando android:exportedsu trueper il provider, ma FileProvidercontrolla internamente se stesso viene esportato, in tal caso genera un'eccezione.


7
+1 questa sembra essere una domanda davvero originale - non c'è niente su Google o StackOverflow , per quanto potrei dire.
Phil

Ecco un post per avviare una configurazione di FileProvider di base utilizzando un progetto di esempio github minimo: stackoverflow.com/a/48103567/2162226 . Fornisce i passaggi per i file da copiare (senza apportare modifiche) in un progetto autonomo locale
Gene Bo,

Risposte:


159

Utilizzando FileProviderdalla libreria di supporto è necessario concedere e revocare manualmente le autorizzazioni (in fase di esecuzione) affinché altre app possano leggere Uri specifici. Utilizzare i metodi Context.grantUriPermission e Context.revokeUriPermission .

Per esempio:

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

Come ultima risorsa, se non riesci a fornire il nome del pacchetto puoi concedere l'autorizzazione a tutte le app in grado di gestire intenti specifici:

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

Metodo alternativo secondo la documentazione :

  • Inserisci l'URI del contenuto in un Intento chiamando setData ().
  • Quindi, chiama il metodo Intent.setFlags () con FLAG_GRANT_READ_URI_PERMISSION o FLAG_GRANT_WRITE_URI_PERMISSION o entrambi.
  • Infine, invia l'intento a un'altra app. Molto spesso, lo fai chiamando setResult ().

    Le autorizzazioni concesse in un Intento rimangono in vigore mentre lo stack dell'attività di ricezione è attivo. Al termine dello stack, le
    autorizzazioni vengono rimosse automaticamente. Le autorizzazioni concesse a
    un'attività in un'app client vengono automaticamente estese ad altri
    componenti dell'app.

Btw. se necessario, è possibile copiare l' origine di FileProvider e modificare il attachInfometodo per impedire al provider di verificare se viene esportato.


26
Utilizzando il grantUriPermissionmetodo dobbiamo fornire il nome del pacchetto dell'app a cui vogliamo concedere l'autorizzazione. Tuttavia, durante la condivisione, di solito non sappiamo quale applicazione sia la destinazione di condivisione.
Randy Sugianto "Yuku",

Che cosa succede se non conosciamo il nome del pacchetto e vogliamo solo che altre app siano in grado di aprirlo
StuStirling,

3
Potete per favore fornire un codice di lavoro per l'ultima soluzione @Lecho?
StErMi

2
Esiste un bug open tracking perché il supporto FileProvider non funziona con setFlags, ma funziona con grantUriPermission? O questo non è un bug, nel qual caso non è un bug?
nmr

1
Oltre a ciò, ho scoperto che la chiamata grantUriPermission non funzionava per un intento creato utilizzando ShareCompat ma lo faceva durante la creazione manuale dell'intento.
StuStirling,

33

Esempio di codice completamente funzionante su come condividere file dalla cartella dell'app interna. Testato su Android 7 e 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>

Codice stesso

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

1
L'uso ShareCompat.IntentBuilderdell'autorizzazione URI e read finalmente lo ha fatto per me.
Cord Rehn,

Pause per altre versioni di Android
Oliver Dixon,

@OliverDixon: quali? L'ho provato su Android 6.0 e 9.0.
Violet Giraffe,

1
Questa è l'unica risposta sull'uso di FileProviderciò che funziona. Le altre risposte sbagliano la gestione dell'intento (che non usano ShareCompat.IntentBuilder) e tale intento non è apribile da app esterne in alcun modo significativo. Ho trascorso letteralmente gran parte della giornata su queste sciocchezze fino a quando finalmente ho trovato la tua risposta.
Violet Giraffe,

1
@VioletGiraffe Non sto usando ShareCompat ma il mio funziona. Il mio problema era che stavo concedendo le autorizzazioni di lettura all'intento del mio selezionatore per errore, quando avevo bisogno di concedere le autorizzazioni di lettura al mio intento PRIMA che fosse passato all'intento di selettore, così come al selettore. Spero che abbia un senso. Assicurati solo di concedere l'autorizzazione in lettura all'intento corretto.
Ryan,

23

Questa soluzione funziona per me dal SO 4.4. Per farlo funzionare su tutti i dispositivi ho aggiunto una soluzione alternativa per i dispositivi più vecchi. Ciò garantisce che venga sempre utilizzata la soluzione più sicura.

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>

Giava:

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'autorizzazione viene revocata con revokeFileReadPermission () nei metodi onResume e onDestroy () del frammento o dell'attività.


1
Questa soluzione ha funzionato per me, ma solo dopo aver aggiunto intent.setDataAndType (uri, "video / *"); BTW manca allegato La variabile
Uri

Vuoi dire che filenon cambierà da onResumea onDestroy?
CoolMind,

16

Dal momento che come dice Phil nel suo commento sulla domanda originale, questo è unico e non ci sono altre informazioni su SO su Google, ho pensato di condividere anche i miei risultati:

Nella mia app FileProvider ha funzionato per condividere i file usando l'intento di condivisione. Non era necessario alcun codice o configurazione speciale, oltre a quello per impostare FileProvider. Nel mio manifest.xml ho inserito:

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

In my_paths.xml ho:

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

Nel mio codice ho:

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

E sono in grado di condividere il mio archivio di file nel mio archivio privato di app con app come Gmail e Google Drive senza alcun problema.


9
Purtroppo questo non funziona. Quel file variabile ToShare funziona solo se il file esiste su una scheda SD o un file system esterno. Il punto di usare FileProvider è che puoi condividere il contenuto dal sistema interno.
Keith Connolly,

Questo sembra funzionare correttamente con i file di archiviazione locali (su Android 5+). Ma ho problemi con Android 4.
Peterdk,

15

Per quanto ne so, funzionerà solo con le versioni più recenti di Android, quindi probabilmente dovrai trovare un modo diverso per farlo. Questa soluzione funziona per me su 4.4, ma non su 4.0 o 2.3.3, quindi non sarà un modo utile per condividere contenuti per un'app che dovrebbe funzionare su qualsiasi dispositivo Android.

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

Prendi nota di come specifichi le autorità. È necessario specificare l'attività da cui verrà creato l'URI e avviare l'intento di condivisione, in questo caso l'attività si chiama SharingActivity. Questo requisito non è ovvio dai documenti di 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>

Fai attenzione a come specifichi il percorso. Le impostazioni di cui sopra sono predefinite alla radice della memoria interna privata.

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

In questo esempio stiamo condividendo un'immagine JPEG.

Infine, è probabilmente una buona idea assicurarsi di aver salvato correttamente il file e di accedervi con qualcosa del genere:

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

8

Nella mia app FileProvider funziona perfettamente e sono in grado di allegare file interni memorizzati nella directory dei file a client di posta elettronica come Gmail, Yahoo ecc.

Nel mio manifest come menzionato nella documentazione di Android ho inserito:

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

E poiché i miei file erano archiviati nella directory dei file root, i filepaths.xml erano i seguenti:

 <paths>
<files-path path="." name="name" />

Ora nel codice:

 File file=new File(context.getFilesDir(),"test.txt");

 Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE);

 shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
                                     "Test");

 shareIntent.setType("text/plain");

 shareIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
                                 new String[] {"email-address you want to send the file to"});

   Uri uri = FileProvider.getUriForFile(context,"com.package.name.fileprovider",
                                                   file);

                ArrayList<Uri> uris = new ArrayList<Uri>();
                uris.add(uri);

                shareIntent .putParcelableArrayListExtra(Intent.EXTRA_STREAM,
                                                        uris);


                try {
                   context.startActivity(Intent.createChooser(shareIntent , "Email:").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));                                                      


                }
                catch(ActivityNotFoundException e) {
                    Toast.makeText(context,
                                   "Sorry No email Application was found",
                                   Toast.LENGTH_SHORT).show();
                }
            }

Questo ha funzionato per me. Spero che questo aiuti :)


1
Da vero MVP per la pubblicazione dei percorsi = "." Ha funzionato per me
robsstackoverflow il

Sto ottenendo un errore del tipo "Impossibile trovare il root configurato che contiene /" Ho usato lo stesso codice
Rakshith Kumar

5

Se ottieni un'immagine dalla fotocamera nessuna di queste soluzioni funziona per Android 4.4. In questo caso è meglio controllare le versioni.

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        uri = Uri.fromFile(file);
    } else {
        uri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".provider", file);
    }
    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
    startActivityForResult(intent, CAMERA_REQUEST);
}

1

solo per migliorare la risposta di cui sopra: se stai ricevendo NullPointerEx:

puoi anche usare getApplicationContext () senza contesto

                List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
                for (ResolveInfo resolveInfo : resInfoList) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }

1

Voglio condividere qualcosa che ci ha bloccato per un paio di giorni: il codice fileprovider DEVE essere inserito tra i tag dell'applicazione, non dopo. Può essere banale, ma non è mai stato specificato, e ho pensato che avrei potuto aiutare qualcuno! (grazie ancora a piolo94)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.