一、关于Android 7.0 FileProvider
- FileProvider是android7.0的产物,但FileProvider并不是最新出来的东西,而是以前就已经存在,由于Android的安全机制 ,一个进程默认不能影响另外一个进程的,如读取私有数据。
- 那么对于进程间的文件的共享 ,出于安全考虑,用FileProvider。FileProvider会基于manifest中的定义定义的一个xml文件(xml目录 下),为所有定义的文件生成content URIs,这样外部的应用在没有权限的情况下,可以通过
授予临时权限
的content uri读取相应的文件。 - FileProvider是v4 support中的类 , 就继承ContentProvider。也就是说content:// Uri 代替了 file:/// Uri. 在Android7.0时候,为了安全,谷歌把它作为了一个强制使用而已。针对file://URI,需要通过FileProvider来转换成content://URI进行访问。
二、那些地方需要FileProvider?
凡是关于文件的访问(写操作)以及使用Intent传递uri的读操作都需要FileProvider。
- Uri.parse
- Uri.fromFile
- file://
- content://
- Context.getFilesDir()
- Environment.getExternalStorageDirectory()
- getCacheDir()
- intent.setDataAndType
出现以上这些api或者符号的地方都应该考虑使用FileProvider进行适配,否则会出现以下异常:
java.io.FileNotFoundException: No content provider
java.lang.IllegalArgumentException: Failed to find configured root that contains
uncaughtException:Failed to find configured root that contains
三、如何使用FileProvider
使用FileProvider需要注意三点:在Manifest.xml中定义一个Provider、在一个xml定义允许Provider临时授权的路径(并配置到Manifest.xml中),最后在代码中传入在Manifest.xml中定义的Provider。即:
第一步:在Manifest.xml中定义Provider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.xxx.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
1、其中需要注意的是android:authorities的格式等于app的包名+Provider命名(此命名不区分大小写),例如:
若包名为:com.angel.app
,Provider命名为:fileProvider
那么:android:authorities = "com.angel.app.fileProvider"
2、android:resource的值为第二步定义的临时授权路径集合。
第二步:在xml中定义临时授权路径集合。
在res中新建xml文件夹,然后在xml文件夹中新建file_paths.xml文件,在该xml中定义要临时授权的路径。
例如:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_storage_root"
path="."/>
<!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
<external-path
name="beta_external_path"
path="Download/"/>
<!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
<external-path
name="beta_external_files_path"
path="Android/data/"/>
<!-- external-path = /storage/emulated/0 -->
<external-path
name="files"
path="Android/data/com.xxx/file/"/> //报名底下的file
<external-path
name="external_image_files_path"
path="DCIM/"/>
<!-- 代表cache目录下的所有文件-->
<cache-path
name="cache_path"
path="."/>
</paths>
在paths规则下,能够定义的标签有五个,分别是:external-path
、external-files-path
、external-cache-path
、files-path
、cache-path
。
那么这些标签对应那些路径呢?又对应哪些API呢?
1、external-path 对应根目录为/storage/emulated/0的路径,即Environment.getExternalStorageDirectory()获取的路径。
2、cache-path 对应于根目录为getCacheDir() api获取的路径。
3、files-path 对应于根目录为Context.getFilesDir()获取的路径。
以上三个是app最常用的访问路径、其余的符号可以通过查询google文档获知。
回到上面配置,name可以是任意命名,只要xml文件定义域内不冲突即可,path值为在标签根目录下授权的具体路径,如external-path中的path为Download/
,那么授权的路径就是:/storage/emulated/0/Download/下所有的文件。
即最终授权的路径为:external-path代表的根路径 + path定义的路径。
第三步:在代码中使用FileProvider
使用FileProvider的代码很简单,只需要在生成uri的时候调用一下FileProvider.getUriForFile 类似的api即可。例如:
// 最后通知图库更新
Uri contentUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
contentUri = FileProvider.getUriForFile(context, "com.xxx.fileProvider", file);
} else {
contentUri = Uri.parse("file://" + file.getAbsolutePath());
}
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, contentUri));
在getUriForFile中使用到的authority参数即为Manifest中定义的authorities的值(需要和Manifest保持一致)。
上面代码对低版本提供的兼容代码,因为android N以下无需进行额外的适配。
四、最后总结
- 国内的系统路径定义混乱,有一些系统没有咱们想授权的路径,这个时候可以使用
<root-path path="" name="root-path" />
进行顶级根目录的全局授权。当然,只是一种偷懒的做法,而且极其不符合FileProvider约束访问设计思想。 - path的值可以为点符号(
"."
),意思为在该根目录下所有的文件夹都可以临时授权访问,只是比root-path稍微好一点的偷懒做法。 - 当出现以下异常的时候,需要考虑是否已经正确配置了FileProvider及在代码引用到。笔者还碰到一个文件就是,在低版本没有在文件路径前面给出
file://
的时候,也出出现此bug。所以要根据android运行的判断定位异常的原因,即android N及以上需要考虑FileProvider的配置正确与否,在android N以下版本需要考虑Uri.parse
生成的串是否正确。
java.io.FileNotFoundException: No content provider: /storage/emulated/0/Android/data/