0%

Android存储路径更新(前章)

参考文章

  • 作者:青蛙要fly
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

什么是适配

摘录 ==> 重学安卓:豁然开朗 存储访问 适配解析

当我们提起适配的时候,我们是在说什么
在思考 “为什么要适配存储访问” 之前,我们先来确认一下,当我们提起适配的时候,我们到底是在说什么 —— 为什么是 “适配”,为什么不是 “改进” 或 “升级” 呢?

1
因为这是一种 历史包袱:适配涉及的是 对已停止维护的老系统的 兼容,以及对新系统新设计的 API 的 支持。

Google 为什么要强制适配

适配通常是出于 隐私安全、硬件匹配 等因素的考虑。硬件匹配,比如这些年 Android 的 “刘海屏状态栏、折叠屏页面重建、全面屏手势” 等等;

而我们今天所谈存储及主要涉及的是 隐私安全,尤其国内盗取用户信息已经成为了常态,而这可以大大提高我们作为用户的安全和隐私保护度。

我们这里再细节一点深究 Android10 的分区存储的强制适配的原因,是因为Android的外部目录,即使App卸载了,原本遗留在该目录下的文件依然存在,这就很容易被被有心之人盯上,所以这是Google开启分区存储的主要原因。

存储访问以及适配

“存储访问”主要是从“效率和复用的角度出发”,透过缓存来提上效率,降低服务器的负担,透过 持久化存储 来实现媒体数据的复用

“存储访问适配”的主要是从“保护用户隐私”出发,透过新的传输机制,来确保本App的私密空间不被越界和泄露

为什么要适配存储访问

接收和发送中的细节。主要的关键就是”缓存“。

  • 收发
  • 1
    2
    3
    4
    1.向服务端发起请求,

    2.收到响应结果,展示到界面(数据在 “内存 RAM” 中),并 缓存一份数据到私有目录(数据被持久化存储到 “闪存 ROM” 中),

1
2
3
1.在内存中编辑数据,点击按钮提交,

2.提交时,会先展示结果到界面、并且 缓存一份数据到私有目录,同时再提交给服务端,

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
2
3
4
5
6
7
8
Android = 10(Q)  新增了分区存储的概念,targetSdkVersion == 29  
Android > 10(Q) 强制开启分区存储的概念,targetSdkVersion >= 30
// 这里我们在罗列上一些开发的细节

当targetSdk <= 28时,应用使用传统存储方式;
当targetSdk <= 29时,可以通过在应用清单的application标签中添加android:requestLegacyExternalStorage="true" ,从而关闭分区存储功能,继续使用传统访问方式。
当targetSdk >= 30时,Android会强制执行分区存储,无法关闭。可以通过Environment.isExternalStorageLegacy() 判断应用存储的运行方式,true表示以传统的兼容方式运行,false表示以分区存储运行

科普 - 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位的模拟器 👍🏻,可以对整体目录有个清晰了解。

为了帮助大家更好的理解,请看这这张脑图,对于开发和理解应该是够用了。

android内部存储-1
android内部存储-2

1
2
3
4
5
6
7
8
9
10
11
12
13

/**
* Environment.getDataDirectory() : /data
* context.getFilesDir() : /data/user/0/com.e.dk_wd/files
* context.getCacheDir() : /data/user/0/com.e.dk_wd/cache
* context.getDataDir() : /data/user/0/com.e.dk_wd
*/
Log.i("----", "Environment.getDataDirectory() : " + Environment.getDataDirectory().absolutePath)
Log.i("----", "context.getFilesDir() : " + this.filesDir!!.absolutePath)
Log.i("----", "context.getCacheDir() : " + this.cacheDir!!.absolutePath)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Log.i("----", "context.getDataDir() : " + this.dataDir!!.absolutePath)
}

如果你想看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 停止运行后,权限便及时作废。

划重点 👆 👆 👆

提示:运行时权限的特质是,只针对被授予的进程,而非对所有进程无差别开放