Android储存方案研究及分析

自从Android Q引入了Scoped Storage,储存问题就像在Kotlin Coroutines之前的Android多线程,八仙过海,各显神通,而且每种方案的都有其长处和短处,Android也有了一万多种储存方案。那么在Google决定开发一个Android StorageX来解决这个问题之前,我自己先来捋一捋,正好最近关于储存的官方文档也更新了。

本文只考虑棒棒糖之后的设备。因为SharedPreferenceSqlite(Room)没啥好说的,本文主要研究MediaStoreFileSAF

File

指的是用于内部储存的getFilesDir()getCacheDir()和外部的getExternalFilesDir()getExternalCacheDir(),不要权限,卸载后会消失。

除了直接用File的API外,还可以用Contextcontext.openFileOutput(filename, Context.MODE_PRIVATE)及其其他方法来达到同样的效果(虽然我觉得后者挺难用而且有点坑)。

MediaStore

READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE when accessing other apps’ files on Android 10 (API level 29) or higher

Permissions are required for all files on Android 9 (API level 28) or lower

官方文档中说,用这个API(MediaStore + ContentResolver)来读写三种内容:图片、音频和视频。Q之前必须要权限,Q及之后读别人的文件才要权限。

这句话意味着Q及之后,用这个方法写入读取自己的写入的文件(Uri)不需要权限的,测试结果也的确是这样的。

这里有个问题,Q及之后是可以通过RELATIVE_PATH把图片保存进相册文件夹中的,但在之前是不能的,这时只能用已被废弃的硬编码 + File来储存了。

此外有一个MediaStore.Downloads,只针对Q及之后,但似乎没啥用?

此外有一个MediaStore.Files,Q之后显示应用创建的图片、音频和视频。

总结一下,Q之前需要读写外部需要权限,Q及之后如果只是读写自己的内容,完全可以不要权限啦。

SAF

上述未包含的其他文件,不需要权限,读写单个文件很方便,文档里很清楚

常见用法

这里列举一些我常用到的情况。

导出/导入一个备份文件

SAF。导出时还能让用户命名和选择位置。不要权限。

分享一个文件

FileProvider + FLAG_GRANT_READ_URI_PERMISSION导出为Uri,然后用ShareCompat分享。文件放内部储存居然也可以。

但是呢,如果项目中用了一些撒币的第三方分享库,而这些垃圾玩意儿只支持分享File和路径,这就有点麻烦了。解决方案有:

  1. 把文件放到应用的外部私有文件夹中,然后把File或路径交给那些没有实现Scoped Storage的应用。但这意味着未来某个Android版本如果强制要求Scoped Storage,又得改代码。
  2. 如果文件是图片的话,可以考虑分享一张小尺寸的Bitmap。
  3. 偷梁换柱。依然使用FileProvider生成的Uri搭配ShareCompat分享,但设置Intent的package为对应应用的包名,如果解析Activity后没有对应的可启动Activity,就清除package后再启动分享选择器。但这么做难以处理某个应用的多种分享方式,比如微信分享和朋友圈分享,要么自己去找对应的Activity名字并承担可能失效的后果,要么只能用对应应用的难用且坑多分享库了。

分享一个Bitmap

Bitmap保存到文件里,再分享一个文件

选择音乐、图片

SAF可以选单个文件,不要权限。选多个文件或者要自定义UI的话,还是乖乖MediaStore + 读取权限吧。