Android 7.0应用间共享文件----FileProvider

应用间经常需要将自己的私有文件共享给其他的app,如某应用想要共享图库的图片用来编辑用户的头像,或者文件管理app允许用户在不同目录下复制粘贴文件等等,但为了保护私有文件的安全性,在targetSdk版本为N或者以后版本的app中,应用的私有目录被限制访问。。。
面向Android7.0的应用,Android框架执行的StrictMode API政策禁止在您的应用外部公开file://URI。如果一项包含文件URI的intent离开您的应用,则您的应用会停止运行,抛出FileUriExposedException

12-09 01:44:45.284 E/AndroidRuntime(17911): FATAL EXCEPTION: main
12-09 01:44:45.284 E/AndroidRuntime(17911): Process: com.example.fileproviderdemo, PID: 17911
12-09 01:44:45.284 E/AndroidRuntime(17911): android.os.FileUriExposedException: file:///data/user/0/com.example.fileproviderdemo/files/image exposed beyond app through ClipData.Item.getUri()
12-09 01:44:45.284 E/AndroidRuntime(17911):     at android.os.StrictMode.onFileUriExposed(StrictMode.java:1958)
...
12-09 01:44:45.290 W/ActivityManager( 1148):   Force finishing activity com.example.fileproviderdemo/.MainActivity

如果要在文件间共享文件,唯一安全的方式就是要发送一项content://URI,并授予URI临时访问权限。这个方式之所以安全,是因为只适用于收到这个URI的应用,且会自动过期。进行此授权最简单的方式就是使用FileProvider类

FileProvider共享文件

FileProvider是ContentProvider的一个特定的子类,通过创建content://类型的uri来替代file://类型的URI,从而为一个app提供了更加安全的文件分享操作。
接下来概述下如何使用FileProvider

在AndroidManifest.xml中定义FileProvider

FileProvider默认提供了为文件创建content Uri的功能,因此就不必再在代码中定义一个她的子类了。
FileProvider也是Android四大基本组件之一,直接在manifest中声明一个FileProvider,如下

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.fileproviderdemo">
    <application>
    ...
        <provider
            android:authorities="com.example.filepandroid:authoritiesroviderdemo.fileprovider"//content uri
            android:name="android.support.v4.content.FileProvider"
            android:grantUriPermissions="true"//设置为true,才可授予临时权限
            android:exported="false">//不需要暴露给外部,设置为false即可
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths"/>//共享的文件目录配置,下一小结具体介绍
        </provider>
...
    </application>

</manifest>

定义可共享的文件目录

FileProvider只会为提前指定好的文件目录生成content URI,通过在xml文件中,以元素指定的文件目录,如下面可为files/images目录下的文件生成content URI:

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

name=”photo” (A URI path segment)为了加强安全性,name定义的值隐藏了你真正要分享的子目录的名字,子目录的名字由path属性定义
path=”images” (The subdirectory you’re sharing你要分享的子目录)

name定义的值是content uri中的路径名,path定义的值是真正子目录的名字
注意:path定义的是一个子目录,而不是一个或多个私密文件,你不能通过文件名,也不能通过文件的子集(用通配符)共享一个文件

paths元素中可包括一个或多个子元素

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path name="name" path="path"/> // files/文件子目录,可通过Context#getFilesDir()获取
    <cache-path name="name" path="path" />// cache/文件子目录,可通过Context#getCacheDir()获取
    <external-path name="name" path="path" />// Environment.getExternalStorageDirectory()
    <external-files-path name="name" path="path" />// Context#getExternalFilesDir(String)
    <external-cache-path name="name" path="path" />// Context#getExternalCacheDir()
</paths>

生成文件的Content Uri

调用FileProvider#getUriForFile()生成文件的Content Uri

 File filePath = new File(getFilesDir(), "images");
 File file = new File(filePath, "myself");
 Uri contentUri = FileProvider.getUriForFile(this, "com.example.fileproviderdemo.fileprovider", file);

上述例子对应的file 文件目录:/data/user/0/com.example.fileproviderdemo/files/images/myself
FileProvider生成的Content Uri是content://com.example.fileproviderdemo.fileprovider/photo/myself
(content://android:authorities/name/fileName)

给content uri授予临时权限

有两种为Content Uri授予临时权限的方式:

方法1: 调用Context.grantUriPermission(package, Uri, mode_flags) 这个方式是只会授权给指定包名的app,权限的有效期:手动调用 Context#revokeUriPermission(Uri uri, int modeFlags)撤销授权,或者重启手机列表内容

//功能,对目标文件进行裁剪,裁剪完并复制到输出文件
//inputUri是要裁剪的目标文件,outUri是裁剪后的输出文件
//Intent中有要传输两个Uri,必须用grantUriPermission方法,方法2只适用于一个uri的情况
Intent intent = new Intent(ACTION_CROP);
intent.setDataAndType(inputUri, "image/");
intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri);
List<ResolveInfo> infoList = getPackageManager().queryIntentActivities(intent, 0);
for (ResolveInfo resolveInfo: infoList) {
    final String packageName = resolveInfo.activityInfo.packageName;
    grantUriPermission(packageName, inputUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    grantUriPermission(packageName, outUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    }
startActivityForResult(intent, CODE_REQUEST_CROP);

方法2: 调用Intent#setFlags (int flags)或者Intent#addFlags (int flags),权限的有效期:当接受到的activity处于活跃状态时持续有效,退出则自动失效,一个activity获取到Content Uri的临时权限,这个权限会延展至这个应用的其他组件

//功能:拍照,并将照片复制到输出文件中
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent, CODE_REQUEST_TAKE_PHOTO);

方法1和方法2 中提到的flag均是Intent#FLAG_GRANT_READ_URI_PERMISSION 或Intent# FLAG_GRANT_WRITE_URI_PERMISSION

将Content Uri共享给其他应用

将Content Uri分享给其他应用,除了上个步骤中的例子通过setData的方案,也可以调用Intent#setClipData() ,只不过这个方法是Android sdk 16才开始支持的

附上测试demo: https://pan.baidu.com/s/1gfMXILH

参考文档:https://developer.android.com/reference/android/support/v4/content/FileProvider.html
https://developer.android.com/about/versions/nougat/android-7.0-changes.html
https://developer.android.com/training/secure-file-sharing/index.html

猜你喜欢

转载自blog.csdn.net/dzkdxyx/article/details/78758322