参考文章
- 作者:青蛙要fly
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
什么是适配
摘录 ==> 重学安卓:豁然开朗 存储访问 适配解析
当我们提起适配的时候,我们是在说什么
在思考 “为什么要适配存储访问” 之前,我们先来确认一下,当我们提起适配的时候,我们到底是在说什么 —— 为什么是 “适配”,为什么不是 “改进” 或 “升级” 呢?
1 | 因为这是一种 历史包袱:适配涉及的是 对已停止维护的老系统的 兼容,以及对新系统新设计的 API 的 支持。 |
Google 为什么要强制适配
适配通常是出于 隐私安全、硬件匹配 等因素的考虑。硬件匹配,比如这些年 Android 的 “刘海屏状态栏、折叠屏页面重建、全面屏手势” 等等;
而我们今天所谈存储及主要涉及的是 隐私安全,尤其国内盗取用户信息已经成为了常态,而这可以大大提高我们作为用户的安全和隐私保护度。
我们这里再细节一点深究 Android10 的分区存储的强制适配的原因,是因为Android的外部目录,即使App卸载了,原本遗留在该目录下的文件依然存在,这就很容易被被有心之人盯上,所以这是Google开启分区存储的主要原因。
存储访问以及适配
“存储访问”主要是从“效率和复用的角度出发”,透过缓存来提上效率,降低服务器的负担,透过 持久化存储 来实现媒体数据的复用
“存储访问适配”的主要是从“保护用户隐私”出发,透过新的传输机制,来确保本App的私密空间不被越界和泄露
为什么要适配存储访问
接收和发送中的细节。主要的关键就是”缓存“。
- 收发
收
1
2
3
41.向服务端发起请求,
2.收到响应结果,展示到界面(数据在 “内存 RAM” 中),并 缓存一份数据到私有目录(数据被持久化存储到 “闪存 ROM” 中),发
1 | 1.在内存中编辑数据,点击按钮提交, |
1.从”效率到平衡“
1.1 对手机来说,有了缓存,下次再加载已经访问的内容时,可以直接从本地调取,而无需重新下载,对app的性能是很大的提高,节省了 时间,流量,电量。
本地闪存速度 300MB/s以上 > https 请求有几十毫秒的延迟。
并且 https 请求涉及 加解密,大量的https 请求意味着大量的运算,损失电量。
1.2 对服务端来说,客户端缓存存在,使得服务器的”超高并发“有所缓减,大幅度降低网络拥堵和服务器奔溃。
2.从”复用到隐私“
Android10(Q)—分区存储的由来
在 Android 版本的不断升级更替中,Google团队对存储目录操作的限制也是越来越严格。
至于为什么要限制,其实是有迹可循的。第一点是出于用户安全数据的保护,Android (Q)10 之前,Android的文件存储现象就像个垃圾桶,只要app取得了存储空间的读写权限,WRITE_EXTERNAL_STORAGE,就可以肆意创建文件,难以管理。
用户体验也特别差,打开文件管理器,会发现,想找个具体的文件根本无从下手。
1 | Android = 10(Q) 新增了分区存储的概念,targetSdkVersion == 29 |
科普 - targetSdkVersion
在开发中targetSdkVersion到底是啥含义?
Android手机的存储
- 安卓设备的物理存储针对app可以分为两大块,内部存储和外部存储(而外部存储包含了私有目录和共享目录)
内部存储:
- 内部存储
- /data/
/app/ : apk /data/ : package:sp,data,webview-cash
/system/
/cache/
/data/data/ : 设备中每一个安装的 App,系统都会在内部存储空间的 data/data 目录下以应用包名为名字自动创建与之对应的文件夹,这个文件夹也用来存放SharedPreferences 和 SQLiteDatabase 的数据, App 中的 WebView 缓存页面信息也在这文件夹下。
Android 6.0以上的手机或者模拟器该路径可能为: /data/data/ 变为 /data/user/0
当app被卸载的时候,这个文件夹 会被删除掉。
开发过程中,可通过 Context对象提供的 API 读取操作 内部存储中的文件
强烈建议大家开发的手机尽量Root或者使用64位的模拟器 👍🏻,可以对整体目录有个清晰了解。
为了帮助大家更好的理解,请看这这张脑图,对于开发和理解应该是够用了。
1 |
|
如果你想看app的看存储的话,提前root,或者你用模拟器打开即可。
外部存储:
现在的手机基本上都是内置了 SD 卡,同时也提供 SD 卡的拓展。
所说的外部存储,就是手机设备内置的 SD卡 和 扩展的SD卡 提供的存储空间
外部存储 也会为 安装的app 提供一块区域(文件夹) ,用来存放私有的文件
通常的路径是:**/storage/emulated/0/Android/data/包名/**
这里提供一下获取方式:
方法 | 路径 |
---|---|
Environment.getExternalStoragePublicDirectory(DIRECTORY_ALARMS) | /storage/sdcard0/Alarms |
Environment.getExternalStoragePublicDirectory(DIRECTORY_DCIM) | /storage/sdcard0/DCIM |
Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS) | /storage/sdcard0/Download |
Environment.getExternalStoragePublicDirectory(DIRECTORY_MOVIES) | /storage/sdcard0/Movies |
Environment.getExternalStoragePublicDirectory(DIRECTORY_MUSIC) | /storage/sdcard0/Music |
Environment.getExternalStoragePublicDirectory(DIRECTORY_NOTIFICATIONS) | /storage/sdcard0/Notifications |
Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES) | /storage/sdcard0/Pictures |
Environment.getExternalStoragePublicDirectory(DIRECTORY_PODCASTS) | /storage/sdcard0/Podcasts |
Environment.getExternalStoragePublicDirectory(DIRECTORY_RINGTONES) | /storage/sdcard0/Ringtones |
分区存储概览
这里我们先看一张图,先大概有个概念
Android存储的改变基本上遵循了3个原则:
更好的从属性: 系统知道哪些文件属于哪些应用,这可以让用户更方便地管理他们的文件。当应用被卸载后,除非用户需要,否则应用之前所创建的文件也不应该保留在设备上;
保护应用数据: 当一个应用将 它所属的文件 写入外部存储时,这些文件是不应该被其他应用所访问的;
保护用户数据: 当用户下载了一些文件,比如带有敏感信息的邮件附件,这些文件应该对其他应用不可见。
Android10分区存储对外部存储重新进行了设计
- 私有存储 (Private Storage) : 每个应用在都拥有自己的私有目录,其它应用看不到,彼此也无法访问到该目录:内部存储私有目录 (/data/data/packageName) ;外部存储私有目录 (/sdcard/Android/data/packageName),
- 共享存储 (Shared Storage) : 存储其他应用可访问文件, 包含媒体文件、文档文件以及其他文件,对应设备DCIM、Pictures、Alarms、Music、Notifications、Podcasts、Ringtones、Movies、Download等目录
私有存储
应用私有目录文件访问方式与之前Android版本一致,可以通过file path获取资源。
这里有个细节点,访问app自身外部私有目录是不需要要任何权限的。
需要注意的不同点是:开启了分区存储特性后,应用只能访问自身的私有空间,即使获得了存储权限,也无法访问其他应用的私有空间
番外篇 – FileProvider 的存在缘由和适配
FileProvider 适配前的混沌世界。
FileProvider 实际上是 Android 4.4 时期便存在的设计,它被强制适配是发生在 Android 7.0。
关于 FileProvider,官方文档 有过这样一段描述:
“每当别的 App 透过 File Uri 来访问你 App 的私有文件时,为了达成访问,需要从底层文件系统将访问权限开放,并且直到权限被收回前,其他任意 App 都是可以访问的。”
如何理解这段话含义?
笔者认为,上述描述中没有提及 “权限被收回的时机”,因而隐含的关键信息是 “时机不可控 和 作用域不受限”,也即,当下虽然只是你 AppA 和 AppB 之间的文件传输,但这文件传输的达成,需要从底层文件系统更改权限,
这就好比,你和朋友约好下午 3 点来你家玩,本来你只须到点开个小窗守着朋友的到来,可你却把自家 前门 后门 左门 右门 统统打开了,这让路过的小偷得以 趁虚而入、盗走他想要的东西。
FileProvider 是如何解决这问题的
很简单,既然 通过 File Uri 访问,涉及底层文件系统访问权限的修改,那改用别的方式便是 —— 通过 Content URI 访问。
这是一种类似于 Web 开发的 “虚拟目录映射” 的设计,通过 Content URI,系统会自动呈现实际路径中的内容,就和你直接通过实际路径访问到的结果是一样的,唯一的区别在于,使用 Content URI 无需开启 “底层文件系统权限”,且 FileProvider 在此基础上增加了“只需授予 运行时级别 临时权限”的设计,如此你俩在传输文件时,潜伏的 App 无法趁虚而入,且 在对方与传输相关的 Activity 离开栈顶或 Service 停止运行后,权限便及时作废。
划重点 👆 👆 👆
提示:运行时权限的特质是,只针对被授予的进程,而非对所有进程无差别开放