如何使用支持FileProvider将内容分享给其他应用程序?

时间:2013-08-15 08:34:47

标签: android android-contentprovider android-support-library android-fileprovider

我正在寻找一种方法,使用Android支持库FileProvider正确地与外部应用程序共享(而不是OPEN)内部文件。

按照文档中的示例,

<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仅授予对intent data指定的Uri的权限,而不授予EXTRA_STREAM extra的值(由setStream设置)

我尝试通过为提供程序设置android:exportedtrue来破坏安全性,但FileProvider在内部检查自身是否已导出,如果是,则会抛出异常。

10 个答案:

答案 0 :(得分:135)

使用支持库中的FileProvider,您必须手动授予和撤消其他应用程序读取特定Uri的权限(在运行时)。使用Context.grantUriPermissionContext.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);
}

根据documentation的替代方法:

  
      
  • 通过调用setData()将内容URI放入Intent中。
  •   
  • 接下来,使用FLAG_GRANT_READ_URI_PERMISSION或FLAG_GRANT_WRITE_URI_PERMISSION调用方法Intent.setFlags()   或两者。
  •   
  • 最后,将意图发送到另一个应用程序。通常,您可以通过调用setResult()来完成此操作。

         

    在Intent中授予的权限在堆栈中保持有效   接收活动的活动是否有效。当堆栈完成时,
      权限会自动删除。授予一个权限的权限   客户端应用程序中的活动会自动扩展到其他应用程序   该应用的组件。

  •   

顺便说一下。如果需要,您可以复制source of FileProvider并更改attachInfo方法,以防止提供商检查是否已导出。

答案 1 :(得分:23)

从OS 4.4开始,这个解决方案对我有用。为了使其适用于所有设备,我为旧设备添加了一种解决方法。这可确保始终使用最安全的解决方案。

的Manifest.xml:

    <provider
        android:name="android.support.v4.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>

爪哇:

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

使用片段或活动的onResume和onDestroy()方法中的revokeFileReadPermission()撤消权限。

答案 2 :(得分:17)

完全正常工作的代码示例如何从内部app文件夹共享文件。在Android 7和Android 5上测试过。

的AndroidManifest.xml

</application>
   ....
    <provider
        android:name="android.support.v4.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);

答案 3 :(得分:14)

据我所知,这只适用于较新版本的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!");
}

答案 4 :(得分:13)

由于菲尔在对原始问题的评论中说,这是独一无二的,谷歌上没有关于SO的其他信息,我想我也应该分享我的结果:

在我的应用程序中,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和谷歌驱动器等应用程序共享我的文件存储,没有任何问题。

答案 5 :(得分:7)

在我的应用程序中FileProvider工作得很好,我能够将存储在files目录中的内部文件附加到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();
                }
            }

这对我有用。希望这有助于:)

答案 6 :(得分:2)

如果您从相机获取图像,这些解决方案都不适用于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);
}

答案 7 :(得分: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);
                }

答案 8 :(得分:1)

我想分享一些阻碍我们几天的事情:将文件提供者代码必须插入应用程序标记之间,而不是插入代码之后。 它可能微不足道,但从未指定,我认为我可以帮助某人! (再次感谢piolo94)

答案 9 :(得分:0)

grantUriPermission(来自 Android 文档)

<块引用>

通常您应该使用 Intent#FLAG_GRANT_READ_URI_PERMISSION 或 Intent#FLAG_GRANT_WRITE_URI_PERMISSION 使用 Intent 直接启动一个活动而不是这个功能。如果你使用这个 直接调用函数,一定要调用 revokeUriPermission(Uri, int) 当目标不再被允许访问时。

所以我进行了测试,我看到了。

  • 如果我们在开始新活动之前使用 grantUriPermission,我们不需要 FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSIONIntent 中克服 SecurityException

  • 如果我们不使用 grantUriPermission。我们需要使用FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION来克服SecurityException但是

    • 您的意图必须包含 Uri by setDatasetDataAndType else SecurityException 仍然抛出。 (我看到一个有趣的事情:setDatasetType 不能很好地协同工作,所以如果您同时需要 Uritype,您需要 setDataAndType。您可以查看内部Intent 代码,目前当你 setType 时,它也会设置 uri=null,当你 setUri 时它也会设置 type=null)