Android适配-文件存储
FileProvider
历史问题:File Uri 访问控制需要开放底层文件系统权限,开放的权限直到下次关闭之对任何 App 应用都可用。这种级别的访问从根本上说是不安全的。
解决方案:FileProvider
Content Uri 访问控制,“路由映射”设计,无需修改开放底层文件系统权限,只需授予运行时级别的临时权限。
AndroidManifest 中声明 FileProvider。考虑到与依赖代码的兼容,不建议直接使用 FileProvider,建议自定义类继承 FileProvider,并制定唯一的 android:authorities 属性。
1
2public class AppleFileProvider extends FileProvider {
}AndroidManifest.xml:
1
2
3
4
5
6
7<provider
android:name="com.XXX.XXX.sample.AppleFileProvider"
android:authorities="${applicationId}.file.provider"
android:exported="false"
android:grantUriPermissions="true">
</provider>- android:name 属性为 FileProvider 组件名。
- android:authorities 属性为控制域,一般是由包名+自定义的标识构成。
- android:exported 属性设置为 false; FileProvider 不需要公开。
- android:grantUriPermissions 属性设置为 true,以允许授予对文件的临时访问权限。
定义“路由映射”关系,即指定 android.support.FILE_PROVIDER_PATHS <meta-data> 元素.
参考官方文档Specifying Available Files
AndroidManifest.xml:
1
2
3
4
5
6
7
8
9
10
11<provider
android:name="com.XXX.XXX.sample.AppleFileProvider"
android:authorities="${applicationId}.file.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/apple_file_provider"/>
</provider>apple_file_provider.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
path="."
name="files_temp" />
<cache-path
path="."
name="cache_temp" />
<external-path
path="."
name="external_temp" />
<external-cache-path
path="."
name="external_cache_temp" />
<external-files-path
path="."
name="external_files_temp" />
<external-media-path
path="."
name="external-media_temp" />
<root-path
path="."
name="root_content_temp" />
</paths>- files-path 表示应用程序内部存储区域子目录中的文件。对应 API Context.getFilesDir()。
- cache-path 表示应用程序内部存储区域的缓存子目录中的文件。对应 API Context.getCacheDir()。
- external-path 表示外部存储区根目录下的文件。对应 API Environment.getExternalStorageDirectory()。
- external-files-path 表示应用程序外部存储区域根目录中的文件。对应 API ContextCompat.getExternalFilesDirs(context, null)。
- external-cache-path 表示应用程序外部缓存区域根目录中的文件。对应 API ContextCompat.getExternalCacheDirs(context)。
- external-media-path 表示应用程序外部媒体区域根目录中的文件。对应 API Context.getExternalMediaDirs()。(API 21+)
注意:root-path 表示设备根目录,可以获取到外置 SD 卡文件。root-path 官方文档里没有提到,但在 FileProvider 源码里有涉及。
1
2
3
4
5
6
7
8
9public class FileProvider extends ContentProvider {
private static final String TAG_ROOT_PATH = "root-path";
private static final String TAG_FILES_PATH = "files-path";
private static final String TAG_CACHE_PATH = "cache-path";
private static final String TAG_EXTERNAL = "external-path";
private static final String TAG_EXTERNAL_FILES = "external-files-path";
private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
private static final String TAG_EXTERNAL_MEDIA = "external-media-path";
}具体“路由映射”逻辑可自行查看 FileProvider 源码中 parsePathStrategy 方法的实现。
运行时获取 content uri。
FileProvider.getUriForFile() 即可将 file Uri 转换为传输时所需的 content Uri。
1
2
3
4
5
6
7
8
9public static Uri getContentUri(Context context, String authority, File file) {
Uri contentUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
contentUri = FileProvider.getUriForFile(context, authority, file);
} else {
contentUri = Uri.fromFile(file);
}
return contentUri;
}
分区存储
存储目录可以参考之前写的文章 Android 存储目录.md。
历史问题:App 开发者未遵循规范,滥用公有存储目录,导致用户相册等公有目录下文件混乱不堪。
解决方案:分区存储。
在分区存储的强制规范下,媒体文件要存放到公有目录,就必须直接或间接地使用 MediaStore 的 API 来统一存储到媒体库。
两个规则强制执行,来规范 App 开发者存储文件行为。
- 强制要求指定相对路径,可以理解为子目录,且必须是公有目录默认媒体目录分类(DCIM、Picture、Music、Video、Download、Document)中的一个。
- Android 11 强制执行分区存储,即使申请了外部空间读写权限,直接通过 File Uri 来读写也会抛出异常。
适配方案:
操作私有目录(包括内部存储私有目录和外部存储私有目录)。
使用 Context 相关 API,无需权限。操作外部存储公有目录。
- SAF (全称 Storage Access Framework,存储访问框架),无需权限即可访问自己或其他 App 的公有媒体文件。SAF 的缺点是界面单调、操作不便、不可定制交互。
- 根据 API 适配。
- Android 4.4 - 10 仍旧使用 File API Environment.getExternalStorageDirectory()。
Android 6、0 需要动态申请外部存储权限。
Android 10 须通过在 manifest 的 Application 节点添加 requestLegacyExternalStorage = true 来声明对 File Api 方式的兼容。 - Android 11 使用 MediaStore 读写,访问自己 App 的文件无需权限。读取其他 App 媒体文件需要 READ_EXTERNAL_STORAGE 权限,写入其他 App 媒体文件需要 MANAGE_EXTERNAL_STORAGE 权限。
- Android 4.4 - 10 仍旧使用 File API Environment.getExternalStorageDirectory()。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!