安卓存储空间
安卓存储结构
首先明确一下新时代下,安卓内部存储和外部存储的概念。我使用的是MIUI12系统,打开文件管理,只能看到内部存储设备。在传统概念中,内部存储设备就是手机自带的空间,外部存储设备是SD卡,但是现在就没有SD卡了,全部用内部存储设备实现,怎么去理解呢?
先看个图了解安卓的文件结构:
-
/data/data/
apk的安装目录。 如:百度地图的安装路径是/data/data/com.baidu.com/ 注意:该目录需要获取root权限才能查看
-
/system/
存放系统应用的apk文件,即手机厂商预安装应用的apk文件 (手机厂商只需把需要预安装的apk放在该节点的相应路径下,android系统就会自己解压并安装该apk)
-
/storage/
该节点是内置存储卡和外置SD卡的挂载点,/storage/emulated/0/是内置存储卡挂载点, /storage/sdcard1是外置SD卡挂载点(不同的设备挂载节点不一样,有些设备可能会挂载到/mnt/节点)。
在AS中写入代码:
//这个方法在API30下已经被废弃了
val dir = Environment.getExternalStorageDirectory()
谷歌官方给这个方法的注释(部分):
这个方法返回主共享/外部存储目录。如果该目录已由用户安装在其计算机上、已从设备中删除或发生了其他一些问题,则当前可能无法访问该目录。您可以使用 getExternalStorageState() 确定其当前状态。
注意:不要被这里的“外部”一词混淆。这个目录最好被认为是媒体/共享存储。它是一个文件系统,可以保存相对大量的数据,并在所有应用程序之间共享(不强制执行权限)。传统上这是一个 SD 卡,但它也可以作为设备中的内置存储实现,该设备与受保护的内部存储不同,可以作为文件系统安装在计算机上。
就是说,外部存储空间作为一个文件系统,被实现在内置存储设备中。
同时,安卓鼓励的是应用运行时重要必须的数据放在内部存储中,而在外部存储空间去进行大文件的存放和共享。
我们上面提到了内置存储卡,它被视为外部存储空间, 所以如果我们尝试打印相关信息:
// 被废弃了,但是依然可以打印出来,在开发中不推荐用
Log.d("外部存储根目录", "${Environment.getExternalStorageDirectory()}")
显示的是 :D/外部存储根目录: /storage/emulated/0
则明确了外部存储的根目录是内置存储卡, 可以理解为烧在主板上的一个硬盘(个人理解)
应用专属存储
内部存储空间
我们可以在内部存储空间给应用创建目录或者文件
这些目录既包括用于存储持久性文件的专属位置,也包括用于存储缓存数据的其他位置。系统会阻止其他应用访问这些位置,并且在 Android 10(API 级别 29)及更高版本中,系统会对这些位置进行加密。这些特征使得这些位置非常适合存储只有应用本身才能访问的敏感数据。 ——Google
内部存储空间中的根目录为/data,下面讨论在内部存储空间中的用户专属存储
我们发现,在context中可以直接调出已经封装好了的用户专属存储的目录:
Log.d("本地文件目录", "${this.filesDir}")
Log.d("本地数据目录", "${this.dataDir}")
Log.d("本地缓存目录","${this.cacheDir}")
显示内容:
D/本地文件目录: /data/user/0/com.example.myapplication/files
D/本地数据目录: /data/user/0/com.example.myapplication
D/本地缓存目录: /data/user/0/com.example.myapplication/cache
这些目录我们可以直接利用来进行创建目录,文件来存取数据,而不需要申请权限
例如,在本地数据目录下创建一个目录, 采用了Context.MODE_PRIVATE
参数:
val dir = getDir("f",Context.MODE_PRIVATE)
Log.d("文件夹", "${dir}")
显示:D/文件夹: /data/user/0/com.example.myapplication/app_f
我们可以在这个文件夹下创建并写入文件(包含内容)
val file = File(dir, "fff.txt")
val fw = FileWriter(file).use {
it.write("牛逼666")
it.close()
}
我们之后在开发的过程中,可以参照上述简单例子的方法,创建内部专属目录并存储文件。
我们也可以把暂时需要的文件写入缓存中,例如用户需要更换头像裁剪生成的中间图片,或者用户加载的内容不期望二次加载,我们都可以把它们存入缓存文件中
在我的上一篇博客中,就把裁剪图片的目标地址定位在缓存文件中, 部分代码:
val outpurDir = this.cacheDir
if(outpurDir.exists()){
cacheFile = File(outpurDir, "${System.currentTimeMillis()}_cache.png")
}
// 在缓存位置的基础上生成Uri
var mPhotoUri = Uri.fromFile(cacheFile)
内部专属存储空间就大概了解到这里。
外部存储空间
我们已经知道,现在的新手机基本采用的是内置存储卡,虽然在用户的角度上看不出内部存储空间和内部存储卡的区别(用户只知道容量为128G),但开发者还是应该有意的把大文件存储在外部存储空间上。
我们可以在外部存储空间上,开设应用私有的存储空间。虽然写在了外部存储空间上,应用被卸载时,这些内容要是会被系统移除。
我们要把什么内容存在上面呢?把一些私有的媒体文件存储在上面合适,避开MediaStore的扫描,让其他的应用无法获取
通过context可以直接获取到外部存储空间的目录:
Log.d("外部存储文件目录", "${this.getExternalFilesDir("MUSIC")}")
显示内容为:D/外部存储文件目录: /storage/emulated/0/Android/data/com.example.myapplication/files/MUSIC
这样创建的音乐目录,MediaStore无法扫描,在安卓10,安卓11下,其他应用也无法直接访问到这个位置,它是安全的。
但我们发现可以获取一个外部的媒体目录(具体做什么现在还不清楚),但可以被MediaStore扫描到
Log.d("外部存储文件目录", this.externalMediaDirs.get(0).absolutePath)
显示内容:
D/外部存储文件目录: /storage/emulated/0/Android/media/com.example.myapplication
所以私有媒体内容我们不应该存入这个文件夹,它很可能被共享(暂未验证)
共享空间
这里的共享是针对应用而言的,例如相册就是一个典型的共享空间,应用都可以去访问相册
Android 提供用于存储和访问以下类型的可共享数据的 API:
- 媒体内容:系统提供标准的公共目录来存储这些类型的文件,这样用户就可以将所有照片保存在一个公共位置,将所有音乐和音频文件保存在另一个公共位置,依此类推。您的应用可以使用此平台的
MediaStore
API 访问此内容。 - 文档和其他文件:系统有一个特殊目录,用于包含其他文件类型,例如 PDF 文档和采用 EPUB 格式的图书。您的应用可以使用此平台的存储访问框架访问这些文件。
媒体内容
这里不做具体的操作介绍,具体的操作介绍直接参考 官方文档:
Mediastore是什么呢,官方称为:
经过优化的媒体集合索引
我们可以把它理解为一个数据库,它的查询方式和SQL数据库查询很类似。
注意的是,在安卓10之后,我们要添加媒体文件都要先生成Uri
文档和其他文件
由于这部分还没有具体使用过,很陌生,具体参考 官方文档
文档和其他特殊文件的操作流程大致为:
系统把文档提供器封装为了一个Activity,通过启动回调的方式来选择文档和处理文档
分区存储
在过去,即API<29时,如果我们去访问外部存储,都需要申请权限,无论是访问媒体库,还是访问我们应用专属分区文件,在早一点的版本,还可以去访问其他应用创建的文件。
为了让用户更好的管理文件,减少混乱,加强安全等级, 分区存储从API29开始提出,API30开始,强制进行分区存储。
分区存储不允许应用去读取其他应用的存储空间,不允许随意读取共享空间(限制了共享的范围),同时我们访问媒体库和应用在外部存储上的专属空间时,不再需要申请权限。
参考 这篇博客,列出分区存储中的一些规则:
- 应用访问自己的应用目录不受限制 无需任何权限
- 应用向媒体集和下载目录提供文件,如果您要想保存图片、视频、音频、文档,无需任何权限
- 不再提供宽泛的共享存储, 读写存储权限只能访问提供的媒体集 (
图片集
、视频集
、音频集
,下载集
) - 位置元数据限制,获取图片上的位置等信息需要请求权限,如果不请求权限,读取图片的信息的时候,位置元数据将会被删除
- 读取 PDF 或其他类型的文件,需要调用系统的文件选择器 (
Storage Access Framerwork API
) - 在媒体集或应用目录之外,写任何文件都需要系统的文件选择器 , 这样用户能选择并确认将文件存在哪里
我们实际操作时,可以概括为以下两点:
- 特定于应用的目录中的文件(使用
getExternalFilesDir()
访问,上述例子给出)。 - 应用创建的照片、视频和音频片段(通过媒体库访问), 尽可能的使用MediaStore去操作
回顾我上一篇博客中从相册选取图片和裁剪图片的例子,都是依照分区存储的思路去处理的,效果很好。
总结
本文浅显地对了解到的安卓存储相关知识的进行了概括。
在应用开发过程中,存储相关内容十分重要,好的存储组织可以提高效率和优化用户体验。后续还会继续深入学习。