前言
随着Android 7.0的到来,为了进一步提高私有文件的安全性,Android不再由开发者放宽私有文件的访问权限,之前我们一直使用"file:///"绝对路径来传递文件地址的方式,在接收方访问时很容易触发SecurityException的异常。
因此,为了更好的适配Android 7.0,例如相机拍照这类涉及到文件地址传递的地方就用上了FileProvider,FileProvider也更好地进入了大家的视野。
其实FileProvider是ContentProvider的一个特殊子类,本质上还是基于ContentProvider的实现,FileProvider会把"file:///"的路径转换为特定的"content://"形式的content uri,接收方通过这个uri再使用ContentResolver去媒体库查询解析。
使用示例
1.在AndroidManifest.xml里声明Provider
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp">
<application
...>
<provider
android:name="androidx.core.content.FileProvider" //指向系统里的FileProvider类
android:authorities="com.example.myapp.fileprovider" //对应你的content uri的基础域名,生成的uri将以content://com.example.myapp.fileprovider作为开头
android:grantUriPermissions="true" //设置允许获取访问uri的临时权限
android:exported="false"//设置不允许导出,我们的FileProvider应该是私有的
>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" //用于设置FileProvider的文件访问路径
/>
</provider>
...
</application>
</manifest>
name直接使用系统的androidx.core.content.FileProvider,如果需要自己继承FileProvider,则在这里写自己的FileProvider,一定要写全名,即:包名+类名。
2.配置FileProvider文件共享的路径
接下来在我们的res目录下创建一个xml目录,在xml目录下新建一个filepaths.xml,这个xml的名字可以根据项目的具体情况来取,对应第一步中mainifest配置里的FileProvider路径的配置中指定的文件。
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="my_images"
path="images/"/>
</paths>
paths元素必须包含以下一个或者多个子元素:
- files-path 对应目录Context.getFilesDir()
- cache-path 对应目录Context.getCacheDir()
- external-path 对应目录Environment.getExternalStorageDirectory()
- external-files-path 对应目录Context,getExternalFilesDir(String) 或者Context.getExternalFilesDir(null)
- external-cache-path 对应目录Context.getExternalCacheDir()。
这一点要谨记,在后面创建文件时会用到。
name属性:name 是分享的文件路径的一部分,它会覆盖要分享的真实的路径,即path指定的路径。 后续生成 content:// URI 时,会使用这个别名代替真实目录名。这样做的目的,很显然是为了提高安全性。这里的name为my_images,所以对应的content uri为
content://com.example.myapp.fileprovider/my_images
path属性:<files-path>标签对应的路径地址为Context.getFilesDir()]()返回的路径地址,而path属性的值则是该路径的子路径,这里的path值为"images/",那组合起来的路径如下所示:
Content.getFilesDir() + "/images/"
name属性跟path属性一一对应,根据上面的配置,当访问到文件"content://com.example.myapp.fileprovider/my_images/xxx.jpg",就会找到这里配置的path路径"Content.getFilesDir() + "/images/xxx.jpg"
示例
<?xml version="1.0" encoding="utf-8"?>
<paths>
//代表的目录即为:Environment.getExternalStorageDirectory()/Android/data/包名/
<external-path
name="files_root"
path="Android/data/包名/" />
//代表的目录即为:Environment.getExternalStorageDirectory()
<external-path
name="external_storage_root"
path="." />
//代表的目录即为:Environment.getExternalStorageDirectory()/pics
<external-path
name="external"
path="pics" />
//意味着Context.getExternalCacheDir()路径下的全部文件
<external-cache-path name="name" path="." />
</paths>
3.使用FileProvider
File imagePath = new File(Context.getFilesDir(), "images");
File photoFile= new File(imagePath, "default_image.jpg");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri uri = FileProvider.getUriForFile(getContext(), "com.example.myapp.fileprovider", photoFile);
} else {
Uri uri = Uri.fromFile(photoFile);
}
getUriForFile方法中的第二个参数要与第一步中在manifest文件里面创建的provider里面的android:authorities名称一样
在file-path中使用name为my_images;
正常路径:/data/data/com.example.myapp/files/default_image.jpg
uri路径:content://com.kgh.test.fileprovider/my_images/default_image.jpg
显然路径被name覆盖了增强了安全性。
4.授予一个uri的临时权限,并将值传给接收方app
我们假设接收方app使用startActivityForResult来请求app的图片资源,则请求方获取请求后根据上面的代码获取
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
用来授予接收方对于文件操作的临时权限,可以设置为 Intent.FLAG_GRANT_READ_URI_PERMISSION或者Intent.FLAG_GRANT_WRITE_URI_PERMISSION或者两者都允许
FLAG_GRANT_READ_URI_PERMISSION——Intent的接受者将被准许执行read操作。
FLAG_GRANT_WRITE_URI_PERMISSION——Intent的接受者将被准许执行write操作。
5.示例
/**
* 从相机获取图片
*/
private void getPicFromCamera() {
//用于保存调用相机拍照后所生成的文件
File mTempFile = new File(this.getExternalCacheDir().getPath() + "/picture/", System.currentTimeMillis() + ".png");
if (!mTempFile.getParentFile().exists()) {
mTempFile.getParentFile().mkdirs();
}
//跳转到调用系统相机
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//判断版本
//如果在Android7.0以上,使用FileProvider获取Uri
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".FileProvider", mTempFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
} else { //否则使用Uri.fromFile(file)方法获取Uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mTempFile));
}
startActivityForResult(intent, 0);
}