如何使用支持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仅授予data对intent 上指定的Uri的许可,而不是EXTRA_STREAMextra 的值(由setStream)。

我试图通过将提供者设置android:exported为来损害安全性true,但是在FileProvider内部检查自身是否已导出,如果导出,则抛出异常。


7
+1这似乎是一个真正原始的问题- 据我所知,Google或StackOverflow没有任何内容。
Phil

这是使用最小的github示例项目获取基本FileProvider设置的文章:stackoverflow.com/a/48103567/2162226 。它提供步骤,其中的文件拷贝过来(不更改),到本地的独立项目
基因博

Answers:


159

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

根据文档的替代方法:

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

    当接收活动的堆栈处于活动状态时,在Intent中授予的权限仍然有效。堆栈完成后,
    权限将自动删除。
    在客户端应用程序中授予一个“ 活动”的权限会自动扩展到
    该应用程序的其他组件。

顺便说一句。如果需要,可以复制FileProvider的源并更改attachInfo方法以防止提供者检查它是否已导出。


26
使用该grantUriPermission方法,我们需要提供要授予其权限的应用程序的程序包名称。但是,共享时,通常我们不知道共享目的地是什么应用程序。
兰迪·苏吉安托

如果我们不知道软件包名称,只是希望其他应用程序能够打开它,该
怎么办

3
您能提供最新解决方案@Lecho的工作代码吗?
StErMi 2014年

2
是否有一个错误跟踪漏洞,说明为什么支持FileProvider不能与setFlags一起使用,但是可以与grantUriPermission一起使用?还是这不是错误,在哪种情况下这不是错误?
nmr 2014年

1
除此之外,我发现GrantUriPermission调用不适用于使用ShareCompat创建的意图,但是在手动创建Intent时起作用。
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上进行了测试。
紫罗兰色长颈鹿

1
这是使用的唯一答案FileProvider。其他答案使意图处理错误(他们没有使用ShareCompat.IntentBuilder),并且该意图无法以任何有意义的方式由外部应用程序打开。我几乎整天都花了这个废话,直到最终找到答案。
紫罗兰色长颈鹿

1
@VioletGiraffe我也没有使用ShareCompat但我的作品。我的问题是,当我需要将意图的读取权限授予传给选择器意图以及选择器之前,我错误地授予了选择者意图的读取权限。我希望这是有道理的。只要确保您授予对正确意图的读取权限。
瑞安

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

在Fragment或Activity的onResume和onDestroy()方法中,使用revokeFileReadPermission()撤消了该权限。


1
这个解决方案对我有用,但是只有在我添加了intent.setDataAndType(uri,“ video / *”);之后,顺便说一句缺少的attachmentUri变量是uri
EdgarK '16

您是说file不会从更改onResumeonDestroy吗?
CoolMind

16

由于正如Phil在对原始问题的评论中所说,这是独特的,并且在google中没有关于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和Google Drive之类的应用共享我的应用私有存储中的文件存储,而不会遇到任何麻烦。


9
不幸的是,这不起作用。仅当文件存在于SD卡或外部文件系统上时,该变量fileToShare才起作用。使用FileProvider的目的是使您可以共享内部系统中的内容。
基思·康诺利

这似乎与本地存储文件(在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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.