Wie verwende ich den FileProvider-Support, um Inhalte für andere Apps freizugeben?

142

Ich suche nach einer Möglichkeit, eine interne Datei mit dem FileProvider der Android Support Library korrekt für eine externe Anwendung freizugeben (nicht zu öffnen) .

Folgen Sie dem Beispiel in den Dokumenten,

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

und Verwenden von ShareCompat zum Freigeben einer Datei für andere Apps wie folgt:

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

funktioniert nicht, da FLAG_GRANT_READ_URI_PERMISSION nur die Berechtigung für den auf der dataAbsicht angegebenen Uri erteilt , nicht den Wert des EXTRA_STREAMExtra (wie von festgelegt setStream).

Ich habe versucht, Kompromisse Sicherheit , indem android:exportedan truefür den Anbieter, sondern FileProviderintern überprüft , ob sich exportiert wird, wenn dem so ist , wirft es eine Ausnahme.

Randy Sugianto 'Yuku'
quelle
7
+1 Dies scheint eine wirklich originelle Frage zu sein - soweit ich das beurteilen kann , gibt es bei Google oder StackOverflow nichts .
Phil
Hier ist ein Beitrag, um ein grundlegendes FileProvider-Setup mit einem minimalen Github-Beispielprojekt in Gang zu bringen : stackoverflow.com/a/48103567/2162226 . Es enthält Schritte, für die Dateien (ohne Änderungen) in ein lokales eigenständiges Projekt kopiert werden können
Gene Bo

Antworten:

159

Wenn FileProviderSie die Support-Bibliothek verwenden, müssen Sie manuell (zur Laufzeit) Berechtigungen erteilen und widerrufen, damit andere Apps bestimmte Uri lesen können. Verwenden Sie die Methoden Context.grantUriPermission und Context.revokeUriPermission .

Beispielsweise:

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

Als letzten Ausweg können Sie, wenn Sie keinen Paketnamen angeben können, allen Apps die Berechtigung erteilen, die bestimmte Absichten verarbeiten können:

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

Alternative Methode gemäß Dokumentation :

  • Fügen Sie den Inhalts-URI in eine Absicht ein, indem Sie setData () aufrufen.
  • Rufen Sie als Nächstes die Methode Intent.setFlags () mit FLAG_GRANT_READ_URI_PERMISSION oder FLAG_GRANT_WRITE_URI_PERMISSION oder beiden auf.
  • Senden Sie die Absicht schließlich an eine andere App. Meistens rufen Sie dazu setResult () auf.

    In einer Absicht erteilte Berechtigungen bleiben wirksam, solange der Stapel der empfangenden Aktivität aktiv ist. Wenn der Stapel fertig ist, werden die
    Berechtigungen automatisch entfernt. Berechtigungen, die für eine
    Aktivität in einer Client-App erteilt wurden, werden automatisch auf andere
    Komponenten dieser App erweitert.

Übrigens. Bei Bedarf können Sie die Quelle von FileProvider kopieren und die attachInfoMethode ändern , um zu verhindern, dass der Anbieter überprüft, ob er exportiert wird.

Leszek
quelle
26
Mit der grantUriPermissionMethode müssen wir den Paketnamen der App angeben, für die wir die Berechtigung erteilen möchten. Bei der Freigabe wissen wir jedoch normalerweise nicht, welche Anwendung das Freigabeziel ist.
Randy Sugianto 'Yuku'
Was ist, wenn wir den Paketnamen nicht kennen und nur möchten, dass andere Apps ihn öffnen können
StuStirling,
3
Können Sie bitte einen Arbeitscode für die neueste Lösung @Lecho bereitstellen?
StErMi
2
Gibt es eine offene Fehlerverfolgung, warum der Support FileProvider nicht mit setFlags funktioniert, sondern mit grantUriPermission? Oder ist das kein Fehler? In welchem ​​Fall ist das kein Fehler?
nmr
1
Darüber hinaus stellte ich fest, dass der Aufruf von grantUriPermission für eine mit ShareCompat erstellte Absicht nicht funktionierte, jedoch beim manuellen Erstellen der Absicht.
StuStirling
33

Voll funktionsfähiges Codebeispiel zum Freigeben von Dateien aus dem inneren App-Ordner. Getestet auf Android 7 und 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>

Code selbst

    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);
Taucher
quelle
1
Die Verwendung der ShareCompat.IntentBuilderund der Lese-URI-Berechtigung hat es schließlich für mich getan.
Cord Rehn
Pausen für andere Versionen von Android
Oliver Dixon
@OliverDixon: welche? Ich habe es auf Android 6.0 und 9.0 getestet.
Violette Giraffe
1
Dies ist die einzige Antwort auf die Verwendung der FileProvider, die funktioniert. Bei den anderen Antworten wird die Absicht falsch behandelt (sie werden nicht verwendet ShareCompat.IntentBuilder), und diese Absicht kann von externen Apps in keiner sinnvollen Weise geöffnet werden. Ich habe buchstäblich den größten Teil des Tages mit diesem Unsinn verbracht, bis ich endlich deine Antwort gefunden habe.
Violette Giraffe
1
@VioletGiraffe Ich benutze auch nicht den ShareCompat, aber meiner funktioniert. Mein Problem war, dass ich versehentlich die Leseberechtigungen für die Absicht meines Wählers erteilte, wenn ich meiner Absicht Leseberechtigungen erteilen musste, bevor sie sowohl an die Absicht des Wählers als auch an den Wähler übergeben wurden. Ich hoffe das ergibt Sinn. Stellen Sie einfach sicher, dass Sie die Leseberechtigung für die richtige Absicht erteilen.
Ryan
23

Diese Lösung funktioniert bei mir seit OS 4.4. Damit es auf allen Geräten funktioniert, habe ich eine Problemumgehung für ältere Geräte hinzugefügt. Dies stellt sicher, dass immer die sicherste Lösung verwendet wird.

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

Die Berechtigung wird mit revokeFileReadPermission () in den Methoden onResume und onDestroy () des Fragments oder der Aktivität widerrufen.

Oliver Kranz
quelle
1
Diese Lösung funktionierte für mich jedoch erst, nachdem ich intent.setDataAndType (uri, "video / *") hinzugefügt hatte. BTW fehlende
AnhangUri
Meinst du, das filewird sich nicht von onResumebis ändern onDestroy?
CoolMind
16

Da dies, wie Phil in seinem Kommentar zur ursprünglichen Frage sagt, einzigartig ist und es in Google keine weiteren Informationen zu SO gibt, dachte ich, ich sollte auch meine Ergebnisse teilen:

In meiner App hat FileProvider sofort Dateien mit der Freigabeabsicht freigegeben. Darüber hinaus war keine spezielle Konfiguration oder Code erforderlich, um den FileProvider einzurichten. In meine manifest.xml habe ich gesetzt:

    <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 habe ich:

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

In meinem Code habe ich:

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

Und ich kann meinen Dateispeicher im privaten Speicher meiner Apps problemlos mit Apps wie Google Mail und Google Drive teilen.

Luke Sleeman
quelle
9
Leider funktioniert das nicht. Diese Variable fileToShare funktioniert nur, wenn die Datei auf einer SD-Karte oder einem externen Dateisystem vorhanden ist. Mit FileProvider können Sie Inhalte aus dem internen System freigeben.
Keith Connolly
Dies scheint mit lokalen Speicherdateien (unter Android 5+) ordnungsgemäß zu funktionieren. Aber ich bekomme Probleme auf Android 4.
Peterdk
15

Soweit ich das beurteilen kann, funktioniert dies nur mit neueren Versionen von Android, sodass Sie wahrscheinlich einen anderen Weg finden müssen, dies zu tun. Diese Lösung funktioniert für mich unter 4.4, jedoch nicht unter 4.0 oder 2.3.3. Daher ist dies kein nützlicher Weg, um Inhalte für eine App freizugeben, die auf einem Android-Gerät ausgeführt werden soll.

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>

Beachten Sie sorgfältig, wie Sie die Behörden angeben. Sie müssen die Aktivität angeben, aus der Sie den URI erstellen und die Freigabeabsicht starten. In diesem Fall heißt die Aktivität SharingActivity. Diese Anforderung ist aus den Google-Dokumenten nicht ersichtlich!

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>

Seien Sie vorsichtig, wie Sie den Pfad angeben. Der obige Wert ist standardmäßig das Stammverzeichnis Ihres privaten internen Speichers.

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 diesem Beispiel teilen wir ein JPEG-Bild.

Schließlich ist es wahrscheinlich eine gute Idee, sich zu vergewissern, dass Sie die Datei ordnungsgemäß gespeichert haben und mit so etwas darauf zugreifen können:

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

In meiner App funktioniert FileProvider einwandfrei und ich kann interne Dateien, die im Dateiverzeichnis gespeichert sind, an E-Mail-Clients wie Google Mail, Yahoo usw. anhängen.

In meinem Manifest, wie in der Android-Dokumentation erwähnt, habe ich Folgendes platziert:

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

Und da meine Dateien im Stammdateiverzeichnis gespeichert wurden, lautete die Datei filepaths.xml wie folgt:

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

Jetzt im Code:

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

Das hat bei mir funktioniert. Hoffentlich hilft das :)

Adarsh ​​Chithran
quelle
1
Du bist ein echter MVP für das Posten von Pfaden = "." Arbeitete für mich
Robsstackoverflow
Ich erhalte
5

Wenn Sie ein Bild von der Kamera erhalten, funktioniert keine dieser Lösungen für Android 4.4. In diesem Fall ist es besser, die Versionen zu überprüfen.

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);
}
CoolMind
quelle
1

Nur um die oben angegebene Antwort zu verbessern: Wenn Sie NullPointerEx erhalten:

Sie können getApplicationContext () auch ohne Kontext verwenden

                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);
                }
Deepak Singh
quelle
1

Ich möchte etwas mitteilen, das uns für ein paar Tage blockiert hat: Der Dateiprovider-Code MUSS zwischen den Anwendungs-Tags eingefügt werden, nicht danach. Es mag trivial sein, aber es wird nie spezifiziert, und ich dachte, ich hätte jemandem helfen können! (Nochmals vielen Dank an piolo94)

Eric Draven
quelle