Android 如何使用support FileProvider将内容共享到其他应用程序?

Android 如何使用support FileProvider将内容共享到其他应用程序?,android,android-contentprovider,android-support-library,android-fileprovider,Android,Android Contentprovider,Android Support Library,Android Fileprovider,我正在寻找一种使用Android支持库与外部应用程序正确共享(而不是打开)内部文件的方法 按照文档上的示例 <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.android.supportv4.my_files" android:grantUriPermissions="true" android:exported

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

按照文档上的示例

<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>
不起作用,因为标志“授予\读取\ URI\权限”仅授予在意图的
数据
上指定的URI权限,而不是
额外\u流
额外(由
设置流
设置)的值


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

使用支持库中的
FileProvider
您必须手动授予和撤销权限(在运行时)用于其他应用读取特定Uri。使用和方法

例如:

//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);
}
//为所有能够处理给定意图的应用授予权限
意图=新意图();
intent.setAction(intent.ACTION\u SEND);
...
List resinfo List=context.getPackageManager().queryInputActivities(intent,PackageManager.MATCH_DEFAULT_仅限);
对于(ResolveInfo ResolveInfo:ResInfo列表){
字符串packageName=resolveInfo.activityInfo.packageName;
grantUriPermission(packageName,uri,Intent.FLAG_GRANT_WRITE_uri_PERMISSION | Intent.FLAG_GRANT_READ_uri_PERMISSION);
}
根据以下标准的替代方法:

  • 通过调用setData()将内容URI放入意图中
  • 接下来,使用FLAG_GRANT_READ_URI_权限或FLAG_GRANT_WRITE_URI_权限调用方法Intent.setFlags() 或者两者兼而有之
  • 最后,将意图发送到另一个应用程序。通常,您可以通过调用setResult()来实现这一点

    在堆栈运行时,在意图中授予的权限保持有效 接收活动的一部分处于活动状态。当堆栈完成时,
    权限将自动删除。授予某人的权限
    客户端应用程序中的活动会自动扩展到其他应用程序
    该应用程序的组件。


顺便说一句。如果你需要,你可以复制和更改
attachInfo
方法,以防止提供商检查它是否被导出。

因为正如菲尔在对原始问题的评论中所说,这是唯一的,在谷歌中没有其他信息,所以我想我也应该分享我的结果:


在我的应用程序中,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>
<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>

而且我可以毫无困难地与Gmail和google drive等应用程序共享我的应用程序专用存储中的文件存储。

据我所知,这只适用于较新版本的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>
    <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>
在本例中,我们共享一幅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!");
}

在我的应用程序中,FileProvider工作得很好,我能够将存储在文件目录中的内部文件附加到Gmail、Yahoo等电子邮件客户端

在Android文档中提到的我的清单中,我放置了:

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

由于我的文件存储在根文件目录中,filepath.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();
                }
            }
File File=new文件(context.getFilesDir(),“test.txt”);
意向共享意向=新意向(android.content.Intent.ACTION\u SEND\u MULTIPLE);
shareIntent.putExtra(android.content.Intent.EXTRA_主题,
“测试”);
shareIntent.setType(“文本/普通”);
shareIntent.putExtra(android.content.Intent.EXTRA_电子邮件、,
新字符串[]{“您要将文件发送到的电子邮件地址”});
Uri=FileProvider.getUriForFile(上下文“com.package.name.FileProvider”,
文件);
ArrayList URI=新的ArrayList();
添加(uri);
shareIntent.putParcelableArrayListExtra(Intent.EXTRA\u流,
URI);
试一试{
context.startActivity(Intent.createChooser(shareIntent,“Email:)).addFlags(Intent.FLAG\u ACTIVITY\u NEW\u TASK));
}
捕获(ActivityNotFounde异常){
Toast.makeText(上下文,
“抱歉,找不到电子邮件应用程序”,
吐司。长度(短)。show();
}
}

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

这个解决方案从OS4.4开始就适用于我。为了使它能在所有设备上运行,我为旧设备添加了一个变通方法。这确保始终使用最安全的解决方案

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

文件_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>
<paths>
    <files-path name="app_directory" path="directory/"/>
</paths>

爪哇:

publicstaticvoidsendfile(上下文){
意向意向=新意向(意向.行动\发送);
intent.setType(“文本/普通”);
字符串dirpath=context.getFilesDir()+File.separator+“directory”;
File File=新文件(dirpath+File.separator+“File.txt”);
Uri=FileProvider.getUriForFile(上下文“com.package.name.FileProvider”,文件);
intent.putExtra(intent.EXTRA_流,uri);
intent.setFlags(intent.FLAG\u GRANT\u READ\u URI\u PERMISSION);
//Android bug的解决方法。
//格拉
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);
    }
}
</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 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);
                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);
                }
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);
}