Android 存储学习

前言:此文是对张绍文在极客时间上的文章做的笔记

Android 存储基础

Android 分区

  1. 什么是分区?
    分区就是将设备中的存储划分为一些互不重叠的部分,每个部分都可以单独的格式化,用作不同的目的。
  2. 使用 adb shell df 命令可以查看当前手机各分区的使用情况。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5ca56L3-1581569925771)(D5E5B4187F544E5D9499436899C6A1A8)]
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oFq2Yi1-1581569925772)(602F54B987C34711BDC78CC31287AA3D)]

Android 存储安全

  1. 权限控制
    Android规定每个应用都应在自己的应用沙盒内运行,这些沙盒为每一个应用创建了唯一的 UID ,各个应用之间不能跨沙盒访问。
  2. 数据加密

Android 存储方式

  1. 什么是存储
    存储就是把特定的数据结构转化成可以被记录和还原(序列化和反序列化)的二进制格式。
  2. 存储方式
    • SharedPreferences

      • 存储一些比较小的 K_V 集合
      • 跨进程不安全 由于没有使用跨进程的锁,在跨进程频繁读写时可能会造成数据的丢失。
      • 加载缓慢 SP文件在加载时使用异步线程,而且加载线程没有使用线程优先级,这就导致主线程等待低优先级线程锁的问题,因此应提前使用异步线程预加载启动过程中用的SP文件。
      • 全量写入 无论是调用 commit() 还是 apply(),即使我们只改动其中的一个条目,都会把整个内容全部写到文件。而且即使我们多次写入同一个文件,SP 也没有将多次修改合并为一次。
      • 卡顿 由于提供了异步落盘 apply 机制,在崩溃或一些异常情况可能会导致数据丢失,所以在收到系统广播或者 被调用onPause() 等一些时机,系统会强制把所有的SP数据落地到磁盘,若没有落地完成,则主线程被一直阻塞。
      • 总结 SP用于存储一些简单,轻量的数据,由于存在全量写入的弊端,SP的文件存储性能与文件大小有关,因此SP文件不能过大。可以看一下微信的 MMKV.
        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C8TPuToH-1581569925773)(81FB0936F2384AE5BE0A35FEA97F1C2B)]
    • ContentProvider

      • 启动性能
        ContentProvider的onCreate()在Application.onCreate()之前调用,而且都是在main线程创建的。因此,自定义ContentProvider的构造函数,静态代 码块,onCreate()都尽量不要做耗时操作,否则会拖慢启动速度。
        ContentProvider提供的query, insert, delete, update都是在Binder线程中执行的。可能存在多线程并发的问题,因此方法内部要做好线程同步。
      • 稳定性
        ContentProvider在进行跨进程数据传递时,利用了 Binder匿名共享内存 机制。概述:通过Binder传递CursorWindow对象内部的匿名共享内存的fd(文件描述符)。这样的话数据不需要跨进程,而是在不同的进程中通过fd来操作同一块匿名内存。
        Android的Binder传输是有大小限制的,一般来说是 1~2M
      • 安全性
        如果在xml中注册ContentProvider时,android:exported="true",若支持执行SQL语句时就需要注意SQL注入的问题;
        如果我们传入的参数是一个文件路径,然后返回文件的内容,这个时候要检验合法性,否则整个应用的私有数据都有可能泄露。
        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u5SWjOca-1581569925773)(A4F9D5A6A8854CBCAF618DA8C5C453A1)]
    • SQLite数据库

      • ORM框架
        ORM也就是对象关系映射,用面向对象的思想把数据库中表和对象关联起来,不操心数据库底层的实现。
        常用的ORM框架 greenDao和Google
        Room
      • 进程与线程并发
        SQLite使用过程中常出现SQLiteDatabaseLockedException.
      android.database.sqlite.SQLiteDatabaseLockedException: database is locked 
      at android.database.sqlite.SQLiteDatabase.dbopen 
      at android.database.sqlite.SQLiteDatabase.openDatabase 
      at android.database.sqlite.SQLiteDatabase.openDatabaseandroid.database.sqlite.SQLiteDatabaseLockedException:
      

      SQLiteDatabaseLockedException归根结底是因为并发导致的,而SQLite的并发有两种: 多进程与多线程并发。

    • 文件存储

    • 网络存储

序列化与反序列化

什么是序列化与反序列化? 当我们需要把一个对象存储到磁盘或者文件与网络时,该对象不能直接传输,它必须进过序列化才能参与传输,序列化便是将对象变成二进制数据的过程;而反序列化便是从磁盘或者文件与网络获取到的二进制数据变成对象的过程。

对象序列化

应用中的对象存储在内存中,如果我们把对象存储下来或者在网络间传输时,这个时候该对象必须进行序列化和反序列的过程。

  1. Serializable
    Serializable可以将对象持久化存储,也可以通过Bundle传递一些Serializable的序列化数据。
    Serializable 的原理是通过 ObjectInputStream 和 ObjectOutputStream 来实现的。

private void writeFieldValues(Object obj, ObjectStreamClass classDesc)  {
    for (ObjectStreamField fieldDesc : classDesc.fields()) {
        ...
        Field field = classDesc.checkAndGetReflectionField(fieldDesc);
        ...

由此可见:整个序列化过程使用了大量的反射和临时变量,而且在序列化对象的时候,不仅会序列化当前对象本身,还需要递归序列化对象引用的其他对象。因此序列化得到性能不乐观。

由于序列化文件包含了非常多的信息,导致它的大小比Class文件本身还要大很多,因此会导致I/O读写上的性能问题。

注意事项

  • 不被序列化的字段。类的static变量以及被声明为transient的字段,正在序列化时会被忽略,不进行序列化存储。
  • 显示声明 serialVersionUID,在实现Serializable时,需要添加一个Serial Version ID,相当于类的版本号。虽然系统会隐私的声明,但是,假如类发生改变,隐私声明的serialVersionUID都会修改,进行反序列化时会产生InvalidClassException 异常。
  • 构造方法 Serializable反序列化时默认不会调用构造方法,因此构造方法中不要进行影响功能的逻辑。
  1. Parcelable
    Parcelable只会在内存中进行序列化操作,并不会将数据存储到磁盘中,因此不是持久化存储。Android 设计它的核心是为了解决:Serializable 在Android中大量跨进程通信时的性能低下问题。

实现Parcelable后,我们需要手动的添加一些代码,实现序列化和反序列化,因此Parcelable并没有使用反射的方式去实现序列化和反序列化,所以比Serializble更高效。

final static class ParcelableIml implements Parcelable{
        int age;
        String name;
        Bundle extra  = Bundle.EMPTY;

        protected ParcelableIml(Parcel in) {
            age = in.readInt();
            name = in.readString();
            extra = in.readBundle();
        }

        public static final Creator<ParcelableIml> CREATOR = new Creator<ParcelableIml>() {
            @Override
            public ParcelableIml createFromParcel(Parcel in) {
                return new ParcelableIml(in);
            }

            @Override
            public ParcelableIml[] newArray(int size) {
                return new ParcelableIml[size];
            }
        };

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(age);
            dest.writeString(name);
            dest.writeBundle(extra);
        }
    }
  1. Serial
    Twitter开源的高性能序列化方案。
  • 由于没有使用反射,相比起传统的反射序列化方案更加高效。
  • 开发者对于序列化过程的控制较强,可定义哪些 Object、Field 需要被序列化。
  • 有很强的 debug 能力,可以调试序列化的过程。
  • 有很强的版本管理能力,可以通过版本号和 OptionalFieldException 做兼容。

数据序列化

  1. JSON
    JSON是一种轻量级的数据交换方式,被广泛使用与网络传输中,是客户端与服务端最常用的通信方式。
  • 相比对象序列化方案,速度更快,体积更小。
  • 相比二进制的序列化方案,结果可读(说明并没有对数据进行加密或者压缩处理,因此),易于排查问题。
  • 使用方便,支持跨平台、跨语言,支持嵌套引用。

JSON解析库 Goodle的 Gson, 阿里爸爸的 FastJson, 美团的 MSON

JSON库的衡量标准

  • 便利性
    支持 JSON 转换成 JavaBean 对象,支持注解,支持更多的数据类型等
  • 性能
    减少反射,减少序列化过程内存与 CPU 的使用,特别是在数据量比较大或者嵌套层级比较深的时候效果会比较明显。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y9HmUJXT-1581569925773)(1C8B89ABAECC49788C9C18A0617B70EC)]
  1. Protocol Buffers
    如果应用的数据量非常大或者对性能有更高的要求,Protocol Buffers是一个比较好的选择。它是Google开源的跨语言编码协议,Google内部几乎所有的RPC都在使用这个协议。
  • 性能。使用了二进制编码压缩,相比 JSON 体积更小,编解码速度也更快。
  • 兼容性。跨语言和前后兼容性都不错,也支持基本类型的自动转换,但是不支持继承与引用类型。
  • 使用成本。Protocol Buffers 的开发成本很高,需要定义.proto 文件,并用工具生成对应的辅助类。辅助类特有一些序列化的辅助方法,所有要序列化的对象,都需要先转化为辅助类的对象,这让序列化代码跟业务代码大量耦合,是侵入性较强的一种方式。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hIdl2X57-1581569925773)(E5949727EACF4DAFB2112DEEF16111D1)]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值