จะใช้การสนับสนุน FileProvider เพื่อแชร์เนื้อหาไปยังแอพอื่น ๆ ได้อย่างไร?


142

ฉันกำลังมองหาวิธีการอย่างถูกต้องหุ้น (ไม่เปิด) ไฟล์ภายในกับโปรแกรมภายนอกใช้ห้องสมุดสนับสนุน Android ของFileProvider

ทำตามตัวอย่างในเอกสาร

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

และใช้ ShareCompat เพื่อแชร์ไฟล์ไปยังแอปอื่นดังนี้:

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

ไม่ทำงานเนื่องจาก FLAG_GRANT_READ_URI_PERMISSION จะให้สิทธิ์แก่ Uri ที่ระบุไว้ในdataเจตนาเท่านั้นไม่ใช่มูลค่าของส่วนEXTRA_STREAMเสริม (ตามที่กำหนดโดยsetStream)

ผมพยายามที่จะรักษาความปลอดภัยการประนีประนอมโดยการตั้งค่าandroid:exportedไปยังtrueผู้ให้บริการ แต่FileProviderภายในตรวจสอบว่าตัวเองจะถูกส่งออกเมื่อเป็นเช่นนั้นก็พ่นยกเว้น


7
+1 นี่ดูเหมือนจะเป็นคำถามที่แท้จริงอย่างแท้จริง - ไม่มีอะไรใน Google หรือStackOverflowเท่าที่ฉันจะบอกได้
Phil

นี่คือการโพสต์ที่จะได้รับการติดตั้ง FileProvider พื้นฐานจะใช้โครงการตัวอย่าง GitHub น้อยที่สุด: stackoverflow.com/a/48103567/2162226 มันมีขั้นตอนที่ไฟล์ที่จะคัดลอก (โดยไม่ทำการเปลี่ยนแปลง) ลงในโครงการแบบสแตนด์อโลนในท้องถิ่น
Gene Bo

คำตอบ:


159

การใช้FileProviderจากห้องสมุดสนับสนุนคุณจะต้องให้สิทธิ์และเพิกถอนการอนุญาตด้วยตัวเอง (ขณะใช้งาน) สำหรับแอปอื่น ๆ เพื่ออ่าน Uri ที่เฉพาะเจาะจง ใช้Context.grantUriPermissionและContext.revokeUriPermissionวิธี

ตัวอย่างเช่น:

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

เป็นทางเลือกสุดท้ายหากคุณไม่สามารถระบุชื่อแพ็คเกจคุณสามารถให้สิทธิ์กับแอพทั้งหมดที่สามารถจัดการกับความตั้งใจเฉพาะ:

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

วิธีทางเลือกตามเอกสาร :

  • ใส่ URI เนื้อหาไว้ในเจตนาโดยการเรียก setData ()
  • ถัดไปเรียกเมธอด Intent.setFlags () ด้วย FLAG_GRANT_READ_URI_PERMISSION หรือ FLAG_GRANT_WRITE_URI_PERMISSION หรือทั้งสองอย่าง
  • สุดท้ายส่งเจตนาไปยังแอปอื่น บ่อยครั้งที่คุณทำสิ่งนี้โดยการเรียก setResult ()

    สิทธิ์ที่ได้รับในเจตนายังคงมีผลบังคับใช้ในขณะที่กิจกรรมการรับได้รับการใช้งานอยู่ เมื่อสแต็กเสร็จสิ้นการ
    อนุญาตจะถูกลบโดยอัตโนมัติ สิทธิ์ที่มอบให้กับ
    กิจกรรมหนึ่งรายการในแอปไคลเอ็นต์จะขยายไปยัง
    คอมโพเนนต์อื่นของแอปนั้นโดยอัตโนมัติ

Btw หากคุณต้องการคุณสามารถคัดลอกแหล่งที่มาของ FileProviderและเปลี่ยนattachInfoวิธีการเพื่อป้องกันไม่ให้ผู้ให้บริการตรวจสอบว่ามีการส่งออกหรือไม่


26
การใช้grantUriPermissionวิธีการที่เราต้องระบุชื่อแพ็คเกจของแอพที่เราต้องการให้อนุญาต อย่างไรก็ตามเมื่อทำการแบ่งปันเรามักจะไม่ทราบว่าแอปพลิเคชันนั้นคือจุดหมายปลายทางใด
Randy Sugianto 'Yuku'

จะเกิดอะไรขึ้นถ้าเราไม่ทราบชื่อแพคเกจและเพียงแค่ต้องการให้แอปอื่น ๆ สามารถเปิดได้
StuStirling

3
คุณช่วยระบุรหัสที่ใช้งานได้สำหรับโซลูชั่นล่าสุด @Lecho ได้ไหม?
StErMi

2
มีข้อผิดพลาดในการติดตามว่าทำไมการสนับสนุน FileProvider ไม่ทำงานกับ setFlags แต่ทำงานกับ GrantUriPermission ได้หรือไม่ หรือนี่ไม่ใช่ข้อผิดพลาดซึ่งในกรณีนี้จะไม่เป็นข้อบกพร่องอย่างไร
nmr

1
นอกจากนี้ฉันพบว่าการเรียกใช้สิทธิ์ GrantUriPermission ไม่ทำงานสำหรับเจตนาที่สร้างขึ้นโดยใช้ ShareCompat แต่จะเกิดขึ้นเมื่อสร้างเจตนาด้วยตนเอง
StuStirling

33

ตัวอย่างโค้ดที่ทำงานได้อย่างสมบูรณ์วิธีแชร์ไฟล์จากโฟลเดอร์แอพภายใน ทดสอบบน Android 7 และ 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>

รหัสตัวเอง

    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
ในที่สุดการใช้ShareCompat.IntentBuilderและการอนุญาตให้อ่าน URI ก็ทำเพื่อฉันแล้ว
Cord Rehn

หยุดพักสำหรับ Android รุ่นอื่น ๆ
Oliver Dixon

@OliverDixon: อันไหน? ฉันทดสอบกับ Android 6.0 และ 9.0
Violet Giraffe

1
นี่เป็นคำตอบเดียวในการใช้FileProviderงาน คำตอบอื่น ๆ ที่ได้รับการจัดการความตั้งใจที่ไม่ถูกต้อง (พวกเขาไม่ได้ใช้ShareCompat.IntentBuilder) และความตั้งใจที่ไม่สามารถเปิดได้โดยแอปภายนอกในทางที่มีความหมายใด ๆ ฉันใช้เวลาเกือบทั้งวันในเรื่องไร้สาระนี้จนได้คำตอบของคุณในที่สุด
Violet Giraffe

1
@VioletGiraffe ฉันยังไม่ได้ใช้ ShareCompat แต่ทำงานได้ ปัญหาของฉันคือฉันให้สิทธิ์การอ่านแก่ผู้เลือกของฉันโดยไม่ได้ตั้งใจเมื่อฉันต้องการให้สิทธิ์การอ่านกับเจตนาของฉันก่อนที่มันจะถูกส่งไปยังเจตนาของนักเลือกเช่นเดียวกับผู้เลือก ฉันหวังว่ามันสมเหตุสมผล เพียงให้แน่ใจว่าคุณได้รับอนุญาตให้อ่านเพื่อความตั้งใจที่ถูกต้อง
Ryan

23

วิธีนี้ใช้ได้สำหรับฉันตั้งแต่ OS 4.4 เพื่อให้มันทำงานบนอุปกรณ์ทั้งหมดฉันได้เพิ่มวิธีแก้ไขสำหรับอุปกรณ์รุ่นเก่า วิธีนี้ช่วยให้มั่นใจได้ว่าจะใช้โซลูชันที่ปลอดภัยที่สุดเสมอ

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

การอนุญาตถูกเพิกถอนด้วย revokeFileReadPermission () ในเมธอด onResume และ onDestroy () ของแฟรกเมนต์หรือกิจกรรม


1
วิธีนี้ใช้ได้ผลสำหรับฉัน แต่หลังจากที่ฉันเพิ่ม intent.setDataAndType (uri, "video / *"); BTW ไม่มีไฟล์แนบ
UU

คุณหมายความว่าfileจะไม่เปลี่ยนจากonResumeเป็นonDestroyใช่หรือไม่
CoolMind

16

เนื่องจากอย่างที่ฟิลกล่าวไว้ในความคิดเห็นของเขาเกี่ยวกับคำถามดั้งเดิมนี่เป็นสิ่งที่ไม่เหมือนใครและไม่มีข้อมูลอื่น ๆ ของ SO บนใน google ฉันคิดว่าฉันควรแชร์ผลลัพธ์ของฉันด้วย:

ในแอปของฉัน FileProvider ทำงานนอกกรอบเพื่อแบ่งปันไฟล์โดยใช้จุดประสงค์ในการแชร์ ไม่จำเป็นต้องมีการกำหนดค่าพิเศษหรือรหัสนอกเหนือไปจากการตั้งค่า FileProvider ใน manifest.xml ของฉันฉันวางไว้:

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

ใน my_paths.xml ฉันมี:

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

ในรหัสของฉันฉันมี:

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

และฉันสามารถแชร์ที่เก็บไฟล์ของฉันในแอปส่วนตัวของฉันด้วยแอพเช่น Gmail และ Google ไดรฟ์


9
น่าเศร้าที่นี่ไม่ได้ fileToShare ตัวแปรนั้นจะทำงานเฉพาะเมื่อไฟล์นั้นมีอยู่ใน SDcard หรือระบบไฟล์ภายนอก จุดประสงค์ของการใช้ FileProvider คือเพื่อให้คุณสามารถแบ่งปันเนื้อหาจากระบบภายใน
Keith Connolly

ดูเหมือนว่าจะทำงานอย่างถูกต้องกับไฟล์ที่จัดเก็บในตัวเครื่อง (บน Android 5+) แต่ฉันพบปัญหาเกี่ยวกับ Android 4
Peterdk

15

เท่าที่ฉันสามารถบอกได้ว่าสิ่งนี้จะใช้ได้กับ Android รุ่นใหม่กว่าเท่านั้นดังนั้นคุณอาจต้องหาวิธีที่แตกต่างออกไป โซลูชันนี้ใช้งานได้กับฉันใน 4.4 แต่ไม่ใช่ใน 4.0 หรือ 2.3.3 ดังนั้นนี่จะไม่เป็นวิธีที่มีประโยชน์ในการแชร์เนื้อหาสำหรับแอพที่มีไว้เพื่อทำงานบนอุปกรณ์ Android ใด ๆ

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

จดบันทึกอย่างระมัดระวังถึงวิธีที่คุณระบุเจ้าหน้าที่ คุณต้องระบุกิจกรรมที่คุณจะสร้าง URI และเรียกใช้ความตั้งใจในการแบ่งปันในกรณีนี้กิจกรรมที่เรียกว่า SharingActivity ข้อกำหนดนี้ไม่ชัดเจนจากเอกสารของ 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>

ระวังวิธีที่คุณระบุเส้นทาง ค่าเริ่มต้นไปที่รูทของที่เก็บข้อมูลส่วนตัวภายในของคุณ

ใน 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"));

ในตัวอย่างนี้เรากำลังแบ่งปันภาพ JPEG

ในที่สุดมันก็เป็นความคิดที่ดีที่จะรับรองตัวเองว่าคุณได้บันทึกไฟล์อย่างถูกต้องและคุณสามารถเข้าถึงไฟล์ได้ด้วยวิธีนี้:

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

ในแอปของฉัน FileProvider ใช้งานได้ดีและฉันสามารถแนบไฟล์ภายในที่เก็บไว้ในไดเรกทอรีไฟล์ไปยังไคลเอนต์อีเมลเช่น Gmail, Yahoo เป็นต้น

ในรายการของฉันตามที่กล่าวไว้ในเอกสาร Android ที่ฉันวาง:

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

และเนื่องจากไฟล์ของฉันถูกเก็บไว้ในไดเรกทอรีรูทไฟล์ filepaths.xml จึงเป็นดังนี้:

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

ตอนนี้ในรหัส:

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

สิ่งนี้ใช้ได้กับฉันหวังว่านี่จะช่วยได้ :)


1
คุณเป็น MVP จริงสำหรับการโพสต์เส้นทาง = "." ทำงานให้ฉัน
robsstackoverflow

ฉันได้รับข้อผิดพลาดเช่น "ล้มเหลวในการค้นหารูทที่กำหนดค่าซึ่งมี /" ฉันใช้รหัสเดียวกัน
Rakshith Kumar

5

หากคุณได้รับภาพจากกล้องโซลูชันเหล่านี้ไม่สามารถใช้กับ Android 4.4 ได้ ในกรณีนี้จะเป็นการดีกว่าที่จะตรวจสอบรุ่น

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

เพียงเพื่อปรับปรุงคำตอบที่ได้รับด้านบน: หากคุณได้รับ NullPointerEx:

คุณยังสามารถใช้ getApplicationContext () โดยไม่มีบริบท

                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

ฉันต้องการแบ่งปันบางอย่างที่ปิดกั้นเราสองสามวัน: รหัส fileprovider ต้องแทรกระหว่างแท็กแอปพลิเคชันไม่ใช่หลังจากนั้น อาจเป็นเรื่องเล็กน้อย แต่ก็ไม่เคยมีการระบุและฉันคิดว่าฉันสามารถช่วยคนได้! (ขอบคุณอีกครั้งที่ piolo94)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.