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

Ran*_*ku' 130 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>
Run Code Online (Sandbox Code Playgroud)

并使用ShareCompat将文件共享给其他应用程序,如下所示:

ShareCompat.IntentBuilder.from(activity)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
Run Code Online (Sandbox Code Playgroud)

不起作用,因为FLAG_GRANT_READ_URI_PERMISSION仅授予data对intent 指定的Uri的权限,而不授予EXTRA_STREAMextra 的值(由set设置setStream).

我试图通过设置成安全android:exportedtrue的供应商,但FileProvider在内部检查,如果本身是出口,所以时,它抛出一个异常.

Les*_*zek 148

使用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);
Run Code Online (Sandbox Code Playgroud)

作为最后的手段,如果您无法提供包名称,则可以向所有可以处理特定意图的应用授予权限:

//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);
}
Run Code Online (Sandbox Code Playgroud)

根据文档的替代方法:

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

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

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

  • 使用`grantUriPermission`方法,我们需要提供我们要授予其权限的应用程序的包名称.但是,在共享时,通常我们不知道共享目的地是什么应用程序. (22认同)
  • 我开了一个错误https://code.google.com/p/android/issues/detail?id=76683 (5认同)
  • 能否为@Lecho提供最新解决方案的工作代码? (3认同)
  • _请注意:_如果您使用“intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)”方法,请记住,您必须首先将数据添加到意图中,然后才添加具有权限的标志。否则,就行不通。 (3认同)
  • 由于 android 11 更改了包可见性,这将不起作用 (3认同)
  • 是否有错误打开跟踪为什么支持FileProvider不能与setFlags一起使用,但是与grantUriPermission一起使用?或者这不是一个错误,在这种情况下,这不是一个错误? (2认同)

Div*_*ers 24

完全工作的代码示例如何从内部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>
Run Code Online (Sandbox Code Playgroud)

XML/provider_paths

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path
        name="share"
        path="external_files"/>
</paths>
Run Code Online (Sandbox Code Playgroud)

代码本身

    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);
Run Code Online (Sandbox Code Playgroud)

  • 这是使用有效的`FileProvider` 的唯一答案。其他答案的意图处理错误(他们不使用`ShareCompat.IntentBuilder`),并且该意图不能被外部应用程序以任何有意义的方式打开。我几乎一天的大部分时间都在胡说八道上,直到最终找到你的答案。 (4认同)
  • `ShareCompat.IntentBuilder` 和读取 URI 权限的使用终于为我做到了。 (3认同)

Oli*_*anz 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>
Run Code Online (Sandbox Code Playgroud)

file_paths.xml:

<paths>
    <files-path name="app_directory" path="directory/"/>
</paths>
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Luk*_*man 15

因为菲尔在对原始问题的评论中说,这是独一无二的,谷歌上没有关于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>
Run Code Online (Sandbox Code Playgroud)

在my_paths.xml我有:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files" path="." />
</paths>
Run Code Online (Sandbox Code Playgroud)

在我的代码中,我有:

    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)));
Run Code Online (Sandbox Code Playgroud)

我可以在我的应用程序私有存储中与Gmail和谷歌驱动器等应用程序共享我的文件存储,没有任何问题.

  • 可悲的是,这不起作用.只有文件存在于SD卡或外部文件系统上时,该变量fileToShare才有效.使用FileProvider的目的是让您可以共享内部系统中的内容. (9认同)

Ras*_*sob 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>
Run Code Online (Sandbox Code Playgroud)

请仔细注意您如何指定权限.您必须指定要从中创建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>
Run Code Online (Sandbox Code Playgroud)

注意如何指定路径.以上默认为您的私有内部存储的根目录.

在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"));
Run Code Online (Sandbox Code Playgroud)

在这个例子中,我们共享一个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!");
}
Run Code Online (Sandbox Code Playgroud)


小智 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>
Run Code Online (Sandbox Code Playgroud)

由于我的文件存储在根文件目录中,因此filepaths.xml如下:

 <paths>
<files-path path="." name="name" />
Run Code Online (Sandbox Code Playgroud)

现在在代码中:

 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();
                }
            }
Run Code Online (Sandbox Code Playgroud)

这对我有用.希望这有帮助:)


Coo*_*ind 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);
}
Run Code Online (Sandbox Code Playgroud)