最近在项目中遇到一个有关FileProvider
的问题,由此来学习了一下FileProvider
,有关笔记记录如下。
FileProvider
是 Android 系统中一个特殊的内容提供者(ContentProvider), 它主要用于应用之间安全的共享文件。通过 FileProvider
,应用可以生成一个 content URI
并授予其它应用临时访问的权限,而不需要将文件的实际路径暴露给其它应用。这样可以有效的避免安全问题。
主要用途
FileProvider
主要应用于以下几种场景:
- 文件共享:当一个应用需要与另外一个应用共享文件时(比如使用Intent发送图片/文件),可以使用
FileProvider
来生成一个content URI
;- 文件访问控制:通过 FileProvider 可以控制其他应用对文件的读写权限,确保文件访问的安全性;
- 避免使用file://URI:由于从Andorid 7.0起,file://URI被弃用,使用 FileProvider 提供的 content URI 可以避免兼容性。
使用步骤
a. 在 AndroidManifest.xml 中声明 FileProvider:
xml
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
b. 配置文件路径
在 res/xml
目录下创建一个 file_paths.xml
文件,定义共享文件的路径。
xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_files" path="." />
</paths>
c. 生成 content URI
使用 FileProvider.getUriForFile()
方法来生成一个 content URI。
java
File file = new File(context.getExternalFilesDir(null), "example.jpg");
Uri contentUri = FileProvider.getUriForFile(context, "com.example.myapp.fileprovider", file);
d. 授予临时权限,并分享
当通过Intent发送文件时,可以授予接收临时访问文件的权限:
java
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "Share image via"));
详细解释 file_paths.xml
在 file_paths.xml
, <external-files-path name="my_files" path="." />
的解释如下:
- external-files-path 用于指定应用的外部私有存储目录中的文件路径。外部私有存储目录是指通过 Context.getExternalFilesDir()方法获取的目录,它通常用于存储应用专属的文件,这些文件会随着应用的卸载而被删除。
- name="my_files": 这是一个可选的属性,用于为这个路径配置起一个别名。在生成的
Content URI
中,这个别名会作为路径的一部分。如果不需要这个别名,那么也可以直接去掉。 - path="."这个属性指定了相对于
external-files-path
目录的相对路径. "."代表当前目录,即整个external-files-path
所在的目录。如果你想指定该目录下的某个子目录,你可以在 path属性中提供相对路劲,例如path="images/".
我们来一个例子:
假设我们的包名是com.xing.dev
,并且在 file_path.xml
中配置了如下内容:
xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_files" path="." />
</paths>
然后通过getExternalFilesDir
获取文件保存目录,并且在该目录下有一个a.png
文件:
java
File file = new File(context.getExternalFilesDir(null), "a.png");
Uri contentUri = FileProvider.getUriForFile(context, "com.xing.dev.fileprovider", file);
那么,此时生成的 contentUri
就应该是如下的样子:
xml
content://com.xing.dev.fileprovider/my_files/a.png
当然,我们还可以配置更加复杂的路径:
如果我现在只想别的应用仅仅共享特定目录下的文件,那么我就可以配置更加具体的路径:
xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="images" path="images/" />
<external-files-path name="docs" path="documents/" />
</paths>
这样,那么就只能访问 /external-files-path
/images/ 和 /external-files-path
/documents/ 文件夹下的文件了。
那么,除了external-files-path
这个外部私有存储目录中的文件路径,file_paths.xml
中还支持哪些tag呢?
xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Environment.getExternalStorageDirectory() -->
<external-path name="external_files" path="." />
<!-- Context.getExternalFilesDir(String) -->
<external-files-path name="external_files_path" path="." />
<!-- Context.getExternalCacheDir() -->
<external-cache-path name="external_cache_path" path="." />
<!-- Context.getFilesDir() -->
<files-path name="internal_files_path" path="." />
<!-- Context.getCacheDir() -->
<cache-path name="internal_cache_path" path="." />
</paths>
目前来说,现在支持的是5个,分别是external-path
,external-files-path
,external-cache-path
, files-path
和 cache-path
, 可以通过下表来查看他们之间的含义:
假设我们的包名还是上面那个:com.xing.dev
:
标签名称 | 对应java代码 | 路径Demo | 含义 |
---|---|---|---|
external-path | Environment.getExternalStorageDirectory() | /storage/emulated/0 | 整个外部存储目录 |
external-files-path | Context.getExternalFilesDir(String) | /storage/emulated/0/Android/data/com.xing.dev/files | 应用的外部私有存储目录中的文件路径 |
external-cache-path | Context.getExternalCacheDir() | /storage/emulated/0/Android/data/com.xing.dev/cache | 应用的外部私有存储目录中的缓存路径 |
files-path | Context.getFilesDir() | /data/user/0/com.xing.dev/files | 应用的内部私有存储目录中的文件路径 |
cache-path | Context.getCacheDir() | /data/user/0/com.xing.dev/cache | 应用的内部私有存储目录中的缓存路径 |
那么,如果我们理解了以上文件目录所代表的含义,那么使用FileProvider
就非常容易了。总结下来,我们就可以有以下步骤:
- 确定我们需要共享文件的位置,是在整个外部存储目录中(这个显然不是特别安全,不推荐使用),还是在外部/内部私有文件/缓存目录中;
- 确定好之后,那么我们可以确定使用哪个标签;
- 确定好标签之后,如果还有更细粒度的要求,我们还可以加上子文件夹;
- 确定好
file_paths.xml
之后,可以通过代码获取对应目标的文件夹,然后生成对应的ContentURI
; - 最后就可以进行分享了。
对于一个越来越安全的android系统,使用FileProvider
将App的存储目录分为5个部分,然后通过各自定义文件配置,实现对文件精细粒度的控制。