Bagaimana cara menggunakan dukungan FileProvider untuk berbagi konten ke aplikasi lain?

142

Saya mencari cara untuk membagikan (bukan OPEN) file internal dengan aplikasi eksternal menggunakan FileProvider pustaka Dukungan Android .

Mengikuti contoh pada dokumen,

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

dan menggunakan ShareCompat untuk berbagi file ke aplikasi lain sebagai berikut:

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

tidak berfungsi, karena FLAG_GRANT_READ_URI_PERMISSION hanya memberikan izin untuk Uri yang ditentukan pada datamaksud, bukan nilai EXTRA_STREAMtambahan (seperti yang ditetapkan oleh setStream).

Saya mencoba untuk keamanan kompromi dengan menetapkan android:exporteduntuk trueuntuk penyedia, tetapi FileProvidersecara internal memeriksa apakah itu sendiri diekspor, ketika begitu, itu melempar pengecualian.

Randy Sugianto 'Yuku'
sumber
7
+1 sepertinya ini adalah pertanyaan yang benar-benar asli - tidak ada apa - apa di Google atau StackOverflow , sejauh yang saya tahu.
Phil
Berikut adalah posting untuk mendapatkan setup FileProvider dasar menggunakan proyek contoh github minimal: stackoverflow.com/a/48103567/2162226 . Ini memberikan langkah-langkah untuk menyalin file mana (tanpa membuat perubahan) menjadi proyek mandiri lokal
Gene Bo

Jawaban:

159

Menggunakan FileProviderdari pustaka dukungan, Anda harus memberikan dan mencabut izin secara manual (saat runtime) agar aplikasi lain dapat membaca Uri tertentu. Gunakan Context.grantUriPermission dan Context.revokeUriPermission metode.

Sebagai contoh:

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

Sebagai upaya terakhir, jika Anda tidak dapat memberikan nama paket, Anda dapat memberikan izin untuk semua aplikasi yang dapat menangani maksud tertentu:

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

Metode alternatif sesuai dengan dokumentasi :

  • Masukkan URI konten dalam Intent dengan memanggil setData ().
  • Selanjutnya, panggil metode Intent.setFlags () dengan FLAG_GRANT_READ_URI_PERMISSION atau FLAG_GRANT_WRITE_URI_PERMISSION atau keduanya.
  • Akhirnya, kirim Intent ke aplikasi lain. Paling sering, Anda melakukan ini dengan memanggil setResult ().

    Izin yang diberikan dalam Intent tetap berlaku saat tumpukan Aktivitas penerima aktif. Ketika tumpukan selesai,
    izin dihapus secara otomatis. Izin yang diberikan untuk satu
    Kegiatan di aplikasi klien secara otomatis diperluas ke
    komponen lain dari aplikasi itu.

Btw. jika perlu, Anda dapat menyalin sumber FileProvider dan mengubah attachInfometode untuk mencegah penyedia memeriksa apakah itu diekspor.

Leszek
sumber
26
Dengan menggunakan grantUriPermissionmetode ini kita perlu memberikan nama paket aplikasi yang ingin kita beri izin. Namun, ketika berbagi, biasanya kita tidak tahu aplikasi apa tujuan berbagi itu.
Randy Sugianto 'Yuku'
Bagaimana jika kita tidak tahu nama paket dan hanya ingin aplikasi lain dapat membukanya
StuStirling
3
Bisakah Anda memberikan kode kerja untuk solusi terbaru @Lecho?
StErMi
2
Apakah ada bug pelacakan terbuka mengapa dukungan FileProvider tidak bekerja dengan setFlags, tetapi apakah bekerja dengan grantUriPermission? Atau apakah ini bukan bug, dalam hal ini bagaimana ini bukan bug?
nmr
1
Selain itu, saya menemukan bahwa panggilan grantUriPermission tidak berfungsi untuk maksud yang dibuat menggunakan ShareCompat tetapi berhasil ketika secara manual membuat Intent.
StuStirling
33

Contoh kode yang berfungsi penuh cara berbagi file dari folder aplikasi dalam. Diuji pada Android 7 dan 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>

Kode itu sendiri

    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);
Penyelam
sumber
1
Penggunaan ShareCompat.IntentBuilderdan izin baca URI akhirnya melakukannya untuk saya.
Cord Rehn
Istirahat untuk versi Android lainnya
Oliver Dixon
@ OliverDixon: yang mana? Saya mengujinya di Android 6.0 dan 9.0.
Violet Giraffe
1
Ini adalah satu-satunya jawaban untuk menggunakan FileProvideryang berfungsi. Jawaban lainnya mendapatkan maksud penanganan yang salah (tidak digunakan ShareCompat.IntentBuilder), dan niat itu tidak dapat dibuka oleh aplikasi eksternal dengan cara apa pun yang bermakna. Saya telah menghabiskan sebagian besar hari ini untuk omong kosong ini sampai akhirnya menemukan jawaban Anda.
Violet Giraffe
1
@VioletGiraffe Saya juga tidak menggunakan ShareCompat tetapi karya saya. Masalah saya adalah bahwa saya memberikan izin baca untuk maksud pemilih saya secara tidak sengaja, ketika saya harus memberikan izin membaca untuk maksud saya SEBELUM itu diberikan kepada niat pemilih, serta pemilih. Saya harap itu masuk akal. Pastikan Anda memberikan izin baca ke maksud yang benar.
Ryan
23

Solusi ini bekerja untuk saya sejak OS 4.4. Untuk membuatnya berfungsi di semua perangkat saya menambahkan solusi untuk perangkat yang lebih lama. Ini memastikan bahwa selalu solusi teraman digunakan.

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>

Jawa:

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

Izin dicabut dengan revokeFileReadPermission () di metode onResume dan onDestroy () dari Fragment atau Activity.

Oliver Kranz
sumber
1
Solusi ini bekerja untuk saya tetapi hanya setelah saya menambahkan intent.setDataAndType (uri, "video / *"); BTW hilang variabel attachmentUri adalah uri
EdgarK
Apakah maksud Anda itu filetidak akan berubah dari onResumemenjadi onDestroy?
CoolMind
16

Karena seperti yang dikatakan Phil dalam komentarnya pada pertanyaan awal, ini unik dan tidak ada info lain tentang SO di google, saya pikir saya juga harus membagikan hasil saya:

Dalam aplikasi saya FileProvider bekerja di luar kotak untuk berbagi file menggunakan maksud berbagi. Tidak diperlukan konfigurasi atau kode khusus, selain itu untuk mengatur FileProvider. Di manifest.xml saya menempatkan:

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

Di my_paths.xml saya punya:

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

Dalam kode saya, saya punya:

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

Dan saya dapat berbagi penyimpanan file di aplikasi pribadi saya dengan aplikasi seperti Gmail dan google drive tanpa masalah.

Luke Sleeman
sumber
9
Sayangnya ini tidak berhasil. FileToShare variabel itu hanya berfungsi jika file ada pada SDcard atau sistem file eksternal. Tujuan menggunakan FileProvider adalah agar Anda dapat berbagi konten dari sistem internal.
Keith Connolly
Ini tampaknya berfungsi dengan baik dengan file penyimpanan lokal (pada Android 5+). Tapi saya mendapatkan masalah di Android 4.
Peterdk
15

Sejauh yang saya tahu ini hanya akan bekerja pada versi Android yang lebih baru, jadi Anda mungkin harus mencari cara yang berbeda untuk melakukannya. Solusi ini berfungsi untuk saya pada 4.4, tetapi tidak pada 4.0 atau 2.3.3, jadi ini bukan cara yang berguna untuk berbagi konten untuk aplikasi yang dimaksudkan untuk berjalan pada perangkat Android apa pun.

Dalam manifes.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>

Perhatikan bagaimana Anda menentukan otoritas. Anda harus menentukan aktivitas dari mana Anda akan membuat URI dan meluncurkan niat berbagi, dalam hal ini kegiatan ini disebut SharingActivity. Persyaratan ini tidak jelas dari dokumen 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>

Hati-hati dengan cara Anda menentukan jalur. Default di atas adalah akar dari penyimpanan internal pribadi Anda.

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

Dalam contoh ini kami membagikan gambar JPEG.

Akhirnya mungkin ide yang bagus untuk meyakinkan diri sendiri bahwa Anda telah menyimpan file dengan benar dan Anda dapat mengaksesnya dengan sesuatu seperti ini:

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

Dalam aplikasi saya, FileProvider berfungsi dengan baik, dan saya dapat melampirkan file internal yang disimpan dalam direktori file ke klien email seperti Gmail, Yahoo, dll.

Dalam manifes saya sebagaimana disebutkan dalam dokumentasi Android saya menempatkan:

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

Dan karena file saya disimpan di direktori file root, filepaths.xml adalah sebagai berikut:

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

Sekarang dalam kode:

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

Ini bekerja untuk saya. Semoga ini membantu :)

Adarsh ​​Chithran
sumber
1
Anda benar-benar MVP untuk memposting jalur = "." Bekerja untuk saya
robsstackoverflow
Saya mendapatkan kesalahan seperti "Gagal menemukan root yang dikonfigurasi yang mengandung /" saya telah menggunakan kode yang sama
Rakshith Kumar
5

Jika Anda mendapatkan gambar dari kamera, tidak ada solusi ini yang berfungsi untuk Android 4.4. Dalam hal ini lebih baik memeriksa versi.

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

hanya untuk meningkatkan jawaban yang diberikan di atas: jika Anda mendapatkan NullPointerEx:

Anda juga dapat menggunakan getApplicationContext () tanpa konteks

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

Saya ingin berbagi sesuatu yang memblokir kami selama beberapa hari: kode fileprovider HARUS dimasukkan di antara tag aplikasi, bukan setelahnya. Ini mungkin sepele, tetapi tidak pernah ditentukan, dan saya pikir saya bisa membantu seseorang! (sekali lagi terima kasih kepada piolo94)

Eric Draven
sumber