代码改变世界

android 常见数据存储

2019-05-31 18:27  般若Android  阅读(886)  评论(0编辑  收藏  举报

Android 存储基础

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

分区名:解释

/system 操作系统预留,用来存储系统文件和框架

/data 存储用户数据

/cache 系统升级过程中使用的分区或者recovery

/vendor 用来存储厂商对Android系统的修改

/storge 外置或者内置sdcard

从表面上看 每个分区非常独立,不同的分区可以使用不同的文件系统,

/system分区:它是存放所有Google提供的Android组件的地方,这个分区只能以只读方式mount,这样主要基于稳定性和安全性考虑,即使发生用户突然断电的情况,也依然需要保证/system分区的内容不会受到破坏和篡改

/data分区:它是所有用户数据存放的地方,主要是为了实现数据隔离,即系统升级和恢复的时候会擦除整个/system分区,但是不影响/data的用户数据,而恢复出厂设置,只会擦除/data的数据

/vendor分区:它是存放厂商特殊系统修改的地方,特别是Android8.0以后,隆重推出了“Treble”项目.厂商OTA时可以只更新自己的/vendor分区即可,让厂商能够以更低的成本、更轻松、更快速地将设备更新到新版Android系统。

Android 存储安全

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

  第一,权限控制

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

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

  第二,数据加密

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

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

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

  Android的全盘加密和文件系统加密方法跟应用的加密有什么不同,我们在应用存储还需要单独的给敏感文件加密吗

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

  常见的数据存储方法

  Android 为我们提供了很多持久化存储的方案,在具体介绍之前,什么是存储?

  存储就是把特定的数据结构转化成可以被记录和被还原的格式,这个数据格式可以是二进制,也可以是xml、JSON、Protocol Buffer这些格式

  对于闪存来说,一切归根到底是二进制的、XML、JSON它们只是提供了一套通用的二进制编解码格式规范。既然有那么多存储方案,那我们在选择存储方法时,一般需要考虑哪些关键因素呢?

  1.关键因素

  在选择数据存储方法时,一般考虑下面几点

  

  数据存储方法不能脱离场景来考虑

  存储选项

  总的来说,我们需要结合应用场景选择合适的数据存储方法。那Android为应用开发者提供了哪些存储数据的方法呢?

  SharedPreferences

  ContentProvider

  文件

  数据库

  第一,SharePreferences的使用

  SharePreferences是Android中比较常用的存储方法,它可以用来存储一些较小的键值对集合

  虽然SharedPreferences使用非常简便,但也是我们诟病比较多的存储方法。它的性能问题比较多,如下

  1.跨进程不安全,由于没有使用跨进程的锁,在跨进程频繁读写有可能导致数据全部丢失。根据线上统计,SP大约有万分之一的损坏率

  2.加载缓慢。SP文件的加载使用的异步线程,而且加载线程并没有设置线程优先级,如果这个时候主线程读取数据就需要等待文件加载线程的结束。这就导致出现主线程等待低于优先级线程锁的问题,比如一个100kb的SP文件读取等待大约需要50-100ms,建议提前用异步线程预加载启动过程用到的SP文件

  3.全量写入。无论调用commit()还是apply()即使我们只改动其中的一个条目,都会把整个内容全部写入文件,而且即使我们多次写入同一个文件,SP也没有将多次修改合并为一次,这也是性能差的重要原因之一

  4.卡顿。由于提供了异步落盘的apply机制,在崩溃或其他一些异常情况可能会导致数据丢失,所以当应用收到系统广播,或者被调用onPause等一些时机,系统会强制吧所有的SP对象数据落地到磁盘,如果没有落地成完成,这时候主线程会一直阻塞,这样非常容易造成卡顿,甚至ANR,

  坦白讲,系统提供的SP的应用场景是用来存储一些非常简单的、轻量级的数据,我们不要使用它来存储过于复杂的数据,例如:HTML、JSON等。而且SP的文件存储性能与文件大小有关,每个SP文件不能过大,我们不要将毫无关联的配置项保存在同一个文件中,同时考虑频繁修改的条目单独隔离出来。

 建议使用MMKV

 

   第二,ContentProvider的使用

  为什么Android系统不把SP设计成跨进程安全的,那是因为Android系统更希望我们这个场景选择使用ContentProvider作为存储方式,ContentProvider作为Android四大组件中的一种,为我们提供了不同进程甚至是不同应用程序之间共享数据的机制

  Android系统中比如相册、日历、音频、视频、通讯录等模块都提供了ContentProvider的访问支持。它的使用十分简单

  使用过程需要注意一下几点。

  1.启动性能

  ContentProvider的生命周期默认在Application onCreat()之前,而且都是在主线程创建的,我们自定义的ContentProvider类的构造函数、静态代码块、onCreat函数都尽量不要做耗时的操作,会拖慢启动速度

  2.稳定性

  ContentProvider在进行跨进程数据传递时,利用了android的Binder和匿名共享内存机制。简单来说,近视通过Binder传递CursorWindow对象内部的匿名共享内存的文件描述符。这样在跨进程传输中,结果数据并不需要跨进程传输,而是在不同进程中通过传输的匿名共享内存文件描述符来操作同一块匿名内存,这样实现不同进程访问相同数据的目的。

  对于I/O,基于mmap的匿名共享内存机制也是有代价的,当传输的数据量非常小的时候,可能不一定划算,所以ContentProvider提供了一种call函数,它会直接通过Binder来传输数据。

  Android Binder传输是有大小限制的,一般来说限制是1-2MB.ContentProvider的接口调用参数和call函数调用并没有使用匿名共享机制,比如要批量插入很多数据,那么就会出现一个插入数据的数组,如果这个数组太大了,那么这个操作就可能出现数据超大异常。

  3.安全性

  虽然ContentProvider为应用程序之间的数据共享提供了很好的安全机制,但是如果ContentProvider是exported,当支持执行SQL语句时就需要注意SQL注入的问题,另外如果我们传入的参数是一个文件路径,然后返回文件的内容,这个时候也要校验合法性,不然整个应用的私有数据都有可能被被人拿到,在intent传递参数的时候可能经常会犯这个错误。

  ContentProvider的要素优缺点

  总结:

  虽然SharePreference和ContentProvider都是我们日常经常使用的存储方法,但是里面确会有大大小小的暗坑,所以我们需要充分了解他们的优缺点,这样在工作中可以更好地使用和优化

  如何在合适的场景选择合适的存储方法是存储优化的必修课,应该学会通过正确性、时间开销、空间开销、安全、开发成本以及兼容性这六大要素来分解某个存储方法。