最近在项目中遇到一个有关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:
<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 文件,定义共享文件的路径。
<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。
File file = new File(context.getExternalFilesDir(null), "example.jpg");
Uri contentUri = FileProvider.getUriForFile(context, "com.example.myapp.fileprovider", file);
d. 授予临时权限,并分享
当通过Intent发送文件时,可以授予接收临时访问文件的权限:
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, 的解释如下:
Content URI中,这个别名会作为路径的一部分。如果不需要这个别名,那么也可以直接去掉。external-files-path 目录的相对路径. "."代表当前目录,即整个 external-files-path 所在的目录。如果你想指定该目录下的某个子目录,你可以在 path属性中提供相对路劲,例如path=“images/”.我们来一个例子:
假设我们的包名是com.xing.dev,并且在 file_path.xml中配置了如下内容:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_files" path="." />
paths>
然后通过getExternalFilesDir获取文件保存目录,并且在该目录下有一个a.png文件:
File file = new File(context.getExternalFilesDir(null), "a.png");
Uri contentUri = FileProvider.getUriForFile(context, "com.xing.dev.fileprovider", file);
那么,此时生成的 contentUri就应该是如下的样子:
content://com.xing.dev.fileprovider/my_files/a.png
当然,我们还可以配置更加复杂的路径:
如果我现在只想别的应用仅仅共享特定目录下的文件,那么我就可以配置更加具体的路径:
<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呢?
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="." />
<external-files-path name="external_files_path" path="." />
<external-cache-path name="external_cache_path" path="." />
<files-path name="internal_files_path" path="." />
<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个部分,然后通过各自定义文件配置,实现对文件精细粒度的控制。