Làm cách nào để sử dụng FileProvider hỗ trợ để chia sẻ nội dung với các ứng dụng khác?


142

Tôi đang tìm cách chia sẻ chính xác (không phải MỞ) một tệp nội bộ với ứng dụng bên ngoài bằng FileProvider của thư viện Hỗ trợ Android .

Theo ví dụ về các tài liệu,

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

và sử dụng ShareCompat để chia sẻ tệp cho các ứng dụng khác như sau:

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

không hoạt động, vì FLAG_GRANT_READ_URI_PERMISSION chỉ cấp quyền cho Uri được chỉ định trên datamục đích, không phải giá trị của phần EXTRA_STREAMbổ sung (như đã được đặt bởi setStream).

Tôi cố gắng để bảo vệ sự thỏa hiệp bằng cách thiết lập android:exportedđể truecho nhà cung cấp, nhưng FileProvidertrong nội bộ kiểm tra xem bản thân được xuất khẩu, khi như vậy, nó ném một ngoại lệ.


7
+1 đây dường như là một câu hỏi thực sự độc đáo - không có trên Google hoặc StackOverflow , theo như tôi có thể nói.
Phil

Đây là một bài đăng để có được một thiết lập FileProvider cơ bản đang sử dụng một dự án ví dụ github tối thiểu: stackoverflow.com/a/48103567/2162226 . Nó cung cấp các bước để sao chép các tập tin (mà không thực hiện thay đổi) vào một dự án độc lập cục bộ
Gene Bo

Câu trả lời:


159

Sử dụng FileProvidertừ thư viện hỗ trợ, bạn phải cấp và thu hồi quyền theo cách thủ công (trong thời gian chạy) cho các ứng dụng khác để đọc Uri cụ thể. Sử dụng Context.grantUriPermissionContext.revokeUriPermission phương pháp.

Ví dụ:

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

Như một phương sách cuối cùng, nếu bạn không thể cung cấp tên gói, bạn có thể cấp quyền cho tất cả các ứng dụng có thể xử lý mục đích cụ thể:

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

Phương pháp thay thế theo tài liệu :

  • Đặt URI nội dung trong một Ý định bằng cách gọi setData ().
  • Tiếp theo, hãy gọi phương thức Intent.setFlags () bằng FLAG_GRANT_READ_URI_PERMISSION hoặc FLAG_GRANT_WRITE_URI_PERMISSION hoặc cả hai.
  • Cuối cùng, gửi ý định đến một ứng dụng khác. Thông thường, bạn làm điều này bằng cách gọi setResult ().

    Các quyền được cấp trong Mục đích vẫn có hiệu lực trong khi ngăn xếp của Hoạt động nhận được kích hoạt. Khi ngăn xếp kết thúc, các
    quyền sẽ tự động bị xóa. Quyền được cấp cho một
    Hoạt động trong ứng dụng khách được tự động mở rộng cho các
    thành phần khác của ứng dụng đó.

Btw. nếu bạn cần, bạn có thể sao chép nguồn của FileProvider và thay đổi attachInfophương thức để ngăn nhà cung cấp kiểm tra xem nó có được xuất hay không.


26
Sử dụng grantUriPermissionphương pháp chúng tôi cần cung cấp tên gói của ứng dụng mà chúng tôi muốn cấp quyền. Tuy nhiên, khi chia sẻ, thông thường chúng ta không biết đích đến chia sẻ là gì.
Randy Sugianto 'Yuku'

Điều gì sẽ xảy ra nếu chúng ta không biết tên gói và chỉ muốn các ứng dụng khác có thể mở nó
StuStirling

3
Bạn có thể vui lòng cung cấp mã làm việc cho giải pháp mới nhất @Lecho không?
StErMi

2
Có lỗi theo dõi mở tại sao FileProvider hỗ trợ không hoạt động với setFlags, nhưng không hoạt động với GrantUriPermission? Hoặc đây không phải là một lỗi, trong trường hợp này làm thế nào đây không phải là một lỗi?
nmr

1
Ngoài ra, tôi thấy rằng lệnh gọi GrantUriPermission không hoạt động cho mục đích được tạo bằng ShareCompat nhưng nó đã thực hiện khi tạo Intent theo cách thủ công.
StuStirling

33

Mẫu mã làm việc đầy đủ làm thế nào để chia sẻ tập tin từ thư mục ứng dụng bên trong. Đã thử nghiệm trên Android 7 và 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 / nhà cung cấp_path

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

Mã chính nó

    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
Việc sử dụng ShareCompat.IntentBuildervà cho phép đọc URI cuối cùng đã làm điều đó cho tôi.
Dây Rehn

Phá vỡ các phiên bản khác của Android
Oliver Dixon

@OliverDixon: cái nào? Tôi đã thử nghiệm nó trên Android 6.0 và 9.0.
Hươu cao cổ Violet

1
Đây là câu trả lời duy nhất về việc sử dụng các FileProvidercông trình đó. Các câu trả lời khác có ý định xử lý sai (họ không sử dụng ShareCompat.IntentBuilder) và ý định đó không thể mở được bởi các ứng dụng bên ngoài theo bất kỳ cách có ý nghĩa nào. Tôi đã dành phần lớn thời gian trong ngày cho điều vô nghĩa này cho đến khi cuối cùng tìm thấy câu trả lời của bạn.
Hươu cao cổ Violet

1
@VioletGiraffe Tôi cũng không sử dụng ShareCompat nhưng tôi làm việc. Vấn đề của tôi là tôi đã cấp quyền đọc cho mục đích của người chọn của tôi do nhầm lẫn, khi tôi cần cấp quyền đọc theo ý định của mình TRƯỚC KHI nó được chuyển cho mục đích của người chọn, cũng như người chọn. Tôi hy vọng điều đó đúng. Chỉ cần chắc chắn rằng bạn đang cấp quyền đọc cho mục đích chính xác.
Ryan

23

Giải pháp này hiệu quả với tôi kể từ OS 4.4. Để làm cho nó hoạt động trên tất cả các thiết bị, tôi đã thêm một cách giải quyết cho các thiết bị cũ hơn. Điều này đảm bảo rằng luôn luôn là giải pháp an toàn nhất được sử dụng.

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

Quyền này bị thu hồi với revokeFileReadPermission () trong các phương thức onResume và onDestroy () của Fragment hoặc Activity.


1
Giải pháp này hiệu quả với tôi nhưng chỉ sau khi tôi thêm ý định.setDataAndType (uri, "video / *"); BTW thiếu tệp đính
kèmUri

Bạn có nghĩa là filesẽ không thay đổi từ onResumeđến onDestroy?
CoolMind

16

Vì như Phil nói trong nhận xét của mình về câu hỏi ban đầu, đây là duy nhất và không có thông tin nào khác về SO trên google, tôi nghĩ tôi cũng nên chia sẻ kết quả của mình:

Trong ứng dụng của tôi, FileProvider hoạt động ngoài hộp để chia sẻ tệp bằng mục đích chia sẻ. Không có cấu hình hoặc mã đặc biệt cần thiết, ngoài điều đó để thiết lập FileProvider. Trong tệp kê khai của tôi, tôi đã đặt:

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

Trong my_paths.xml tôi có:

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

Trong mã của tôi, tôi có:

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

Và tôi có thể chia sẻ lưu trữ tệp của mình trong kho ứng dụng của mình với các ứng dụng như Gmail và google drive mà không gặp rắc rối nào.


9
Đáng buồn là điều này không hoạt động. Tệp biến đó ToShare chỉ hoạt động nếu tệp tồn tại trên thẻ SDcard hoặc hệ thống tệp bên ngoài. Điểm quan trọng của việc sử dụng FileProvider là để bạn có thể chia sẻ nội dung từ hệ thống nội bộ.
Keith Connolly

Điều này dường như hoạt động đúng với các tệp lưu trữ cục bộ (trên Android 5+). Nhưng tôi gặp sự cố trên Android 4.
Peterdk

15

Theo như tôi có thể nói điều này sẽ chỉ hoạt động trên các phiên bản Android mới hơn, vì vậy bạn có thể sẽ phải tìm ra một cách khác để làm điều đó. Giải pháp này hoạt động với tôi vào ngày 4.4, nhưng không phải trên 4.0 hoặc 2.3.3, vì vậy đây sẽ không phải là cách hữu ích để chia sẻ nội dung cho một ứng dụng có nghĩa là chạy trên mọi thiết bị Android.

Trong tệp kê khai:

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

Hãy lưu ý cẩn thận về cách bạn chỉ định các cơ quan chức năng. Bạn phải chỉ định hoạt động mà từ đó bạn sẽ tạo URI và khởi chạy mục đích chia sẻ, trong trường hợp này, hoạt động được gọi là SharingActivity. Yêu cầu này không rõ ràng từ tài liệu của 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>

Hãy cẩn thận làm thế nào bạn chỉ định đường dẫn. Các mặc định ở trên đến thư mục gốc của bộ nhớ trong riêng của bạn.

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

Trong ví dụ này, chúng tôi đang chia sẻ một hình ảnh JPEG.

Cuối cùng, có lẽ bạn nên tự đảm bảo rằng mình đã lưu tệp đúng cách và bạn có thể truy cập tệp đó bằng một cái gì đó như thế này:

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

Trong ứng dụng của tôi, FileProvider chỉ hoạt động tốt và tôi có thể đính kèm các tệp nội bộ được lưu trong thư mục tệp vào các ứng dụng email như Gmail, Yahoo, v.v.

Trong bảng kê khai của tôi như được đề cập trong tài liệu Android tôi đã đặt:

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

Và vì các tệp của tôi được lưu trữ trong thư mục tệp gốc, tệp filepaths.xml như sau:

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

Bây giờ trong mã:

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

Điều này làm việc cho tôi. Hy vọng điều này sẽ giúp :)


1
Bạn da MVP thực sự cho đăng đường dẫn = "." Làm việc cho tôi
robsstackoverflow

Tôi gặp lỗi như "Không thể tìm thấy root được cấu hình có chứa /" tôi đã sử dụng cùng một mã
Rakshith Kumar

5

Nếu bạn nhận được hình ảnh từ camera thì không có giải pháp nào trong số này hoạt động cho Android 4.4. Trong trường hợp này, tốt hơn là kiểm tra các phiên bản.

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

chỉ để cải thiện câu trả lời được đưa ra ở trên: nếu bạn đang nhận được NullPulumEx:

bạn cũng có thể sử dụng getApplicationContext () mà không cần ngữ cảnh

                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

Tôi muốn chia sẻ một cái gì đó đã chặn chúng tôi trong một vài ngày: mã fileprovider PHẢI được chèn giữa các thẻ ứng dụng, không phải sau nó. Nó có thể là tầm thường, nhưng nó không bao giờ được chỉ định, và tôi nghĩ rằng tôi có thể đã giúp được ai đó! (một lần nữa xin cảm ơn piolo94)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.