Android 存储优化系列专题

闪存
Android 存储优化系列专题
  • SharedPreferences 系列

Android 之不要滥用 SharedPreferences(上)
Android 之不要滥用 SharedPreferences(下)

  • ContentProvider 系列(待更)

Android 存储选项之 ContentProvider 启动存在的暗坑
《Android 存储选项之 ContentProvider 深入分析》

  • 对象序列化系列

Android 对象序列化之你不知道的 Serializable
Android 对象序列化之 Parcelable 取代 Serializable ?
Android 对象序列化之追求性能完美的 Serial

  • 数据序列化系列(待更)

《Android 数据序列化之 JSON》
《Android 数据序列化之 Protocol Buffer 使用》
《Android 数据序列化之 Protocol Buffer 源码分析》

  • SQLite 存储系列

Android 存储选项之 SQLiteDatabase 创建过程源码分析
Android 存储选项之 SQLiteDatabase 源码分析
数据库连接池 SQLiteConnectionPool 源码分析
SQLiteDatabase 启用事务源码分析
SQLite 数据库 WAL 模式工作原理简介
SQLite 数据库锁机制与事务简介
SQLite 数据库优化那些事儿


Android 存储基础

在讲具体的存储方法之前,我们先来了解下 Android 系统存储相关的一些基础知识。

1. Android 分区

说到存储,对于 Android 来说,我们首先应该对 Android 分区的架构和作用有所了解。在大家熟悉的 Windows 世界里,系统一般安装在 C 盘,然后还会有几个用来存放应用程序和数据的分区。

Android 系统可以通过 /proc/partitions 或者 df 命令来查看各个分区情况,下图是华为 Magic 2 中 df 命令的运行结果。

df分区

什么是分区呢?分区简单来说就是将设备中的存储划分为一些不重叠的部分,每个部分都可以单独格式化,用作不同的目的。这样系统就可以灵活地针对单独分区做不同的操作,例如在系统还原(recovery)过程,我们不希望会影响到用户存储的数据。

Android系统分区

从上面的表中你可以看到,每个分区非常独立,不同的分区可以使用的不同的文件系统。其中比较重要的有:

  • /system 分区:它是存放所有 Google 提供的 Android 组件的地方。这个分区只能以只读方式 mount。这样主要基于稳定性和安全考虑,即使发生用户突然断电的秦光,也依然需要保证 /system 分区的内容不会受到破坏和篡改。
  • /data 分区:它是所有用户数据存放的地方。主要是为了实现数据隔离,既系统升级和恢复的时候会擦出整个 /system 分区,但是却不会影响 /data 的用户数据。而恢复出厂设置,只会擦出 /data 的数据。
  • /vendor 分区:它是存放厂商特殊系统修改的地方。特别是在 Android 8.0 以后,隆重推出了 “Treble” 项目。厂商 OTA 时可以只更新自己的 /vendor 分区即可,让厂商能够以更低的成本,更轻松、更快速地将设备更新到新版 Android 系统。
2. Android 存储安全

除了数据的分区隔离,存储安全也是 Android 系统非常重要的一部分,存储安全首先考虑的是权限控制。

第一,权限控制

Android 的每个应用都在自己的应用沙盒内运行,在 Android 4.3 之前的版本中,这些沙盒使用了标准 Linux 的保护机制,通过为每个应用创建独一无二的 Linux UID 来定义。简单来说,就是需要保证微信不能访问 QQ 的数据,并且在没有权限的情况下也不能访问系统的一些保护文件。

在 Android 4.3 引入了 SELinux(Security Enhanced Linux)机制进一步定义 Android 应用沙盒的边界。那它有什么特别的呢?它的作用是即使我们进程有 root 权限也不能为所欲为,如果想在 SELinux 系统中干任何事情,都必须现在专门的安全策略文件中赋予权限。

第二,数据加密

除了权限的控制,用户还会担心在手机丢失或者被盗导致个人隐私数据泄露。加密获取是一个不错的选择,它可以保护丢失或被盗设备上的数据。

Android 有两种设备加密方法:全盘加密和文件级加密。全盘加密是在 Android 4.4 中引入的,并在 Android 5.0 中默认打开。它会将 /data 分区的用户数据操作加密 / 解密,对性能会有一定的影响,但是新版本的芯片都会在硬件中提供直接支持。

基于文件系统的加密,如果设备被解锁了,加密也就没有用了。所以 Android 7.0 增加了基于文件的加密。在这种加密模式下,将会给每个文件都分配一个必须用用户的 passcode 推导出来的秘钥。特定的文件被屏幕锁屏之后,直到用户下次解锁屏幕期间都不能访问。

那 Android 的这两种设备加密方法跟应用的加密有什么不同,我们在应用存储还需要单独的给敏感文件加密吗?

其实设备加密方法对应用程序来说是透明的,它保证我们读取到的是解密后的数据。对于应用程序特别敏感的数据,我们也需要采用 RSA、AES、chacha20 等常用方式做进一步的存储加密。

常见的数据存储方法

Android 为我们提供了很多种持久化存储的方案,存储实际就是把特定的数据结构转化成可以被记录和还原的格式,这个数据格式可以是二进制的,也可以是 XML、JSON、Protocol Buffer 这些格式。

对于闪存来说,一切归根到底还是二进制的,XML、JSON 它们只是提供了一套通用的二进制编码格式规范。

1. 关键要素

既然有那么多的数据存储的方案,那我们在选择数据存储方法时一般需要考虑哪些关键因素呢?这里直接借鉴大神的总结给你。

要素 描述
正确性 选择存储方案的时候,第一个需要判断它是否靠谱。这套存储方案设计是否完备,有没有支持多线程或者跨进程同步操作。内部是否健壮,有没有考虑异常情况下数据的检验和恢复,比如采用双写或者备份文件策略,即使主文件因为系统底层导致损坏,也可以一定程度上恢复大部分数据。
时间开销 这里说的时间开销包括了CPU时间和I/O时间,I/O操作相比CPU和内存,I/O存储的速度是非常慢的。但是如果存储方法中比如编解码或者加密/解密等设计的比较复杂,整个数据存储过程也会出现CPU时间变得更长的情况。
空间开销 即使相同的数据如果使用不同的编码方式,最后占用的存储空间也会有所不同。举一个简单的例子,相同的数据所占的空间大小是XML>JSON>Protocol Buffer。除了编码方式的差异,在一些场景我们可能还需要引入压缩策略进一步减少存储空间,例如zip、lzma等。数据存储的空间开销还需要考虑到内存空间的占用量,整个存储过程会不会导致应用出现大量GC、OOM等。
安全 应用中可能会有一些非常敏感的数据,即使它们存储在data/data中,我们依然必须将它们加密。根据加密的强度不同,可以选择RSA、AES、chacha20、TEA这些常用的加密算法。
开发成本 有些存储方案看起来非常高大上,但是需要业务做很大改造才能接入。这里我们当然希望无缝的接入到业务中,在整个开发过程中越简单越好。
兼容性 业务不停地向前演进,我们的存储字段或者格式有时候也会不得不有所变化。兼容性首先考虑的是向前、向后的兼容性,老的数据在升级时能能够迁移过来,新的数据在老版本能否降级使用。兼容性另外一个需要考虑的可能是多语言的问题,不同的语言是否支持转换。

那上面这些要素哪个最重要呢?数据存储方法不能脱离场景来考虑,我们不可能把这六要素都做成完美:如果首要考虑的是正确性,那我们可能需要采用冗余、双写等方案,那就要容忍对事件开销产生的额外影响。同样如果非常在意安全,加解密环节的开销也不必可少。如果想针对启动场景,我们希望选择在初始化时间和读取时间更有优势的方案。

2. 存储选项

总的来说,应用需要结合使用场景选择合适的数据存储方法。Android 为应用提供的数据存储方法你可以参考存储选项,总体看有下面几种方法。

  • SharedPreferences
  • ContentProvider
  • 文件
  • 数据库

3. 数据存储选项

  • Serializable
  • Parcelable
  • Serial(Twitter 开源)

它们的原理以及源码解析你可以参考文章开头给出的分析系列。

推荐阅读更多精彩内容