Android开发经验谈Android开发

Android 优化——存储优化

2018-07-09  本文已影响52人  三流之路

Android 优化目录


交换数据格式

Google 推出的 Protocal Buffers 是一种更轻便高效的存储结构,但消耗内存较大。

FlatBuffers 同样由 Google 推出,专注性能,适合移动端。占用存储比 Protocal 要大。

SharePreferences 优化

Bitmap 解码

数据库优化

  1. 使用 StringBuilder 代替 String

  2. 查询时返回更少的结果集及更少的字段

    查询时只取需要的字段和结果集,更多的结果集会消耗更多的时间及内存,更多的字段会导致更多的内存消耗。

  3. 少用 cursor.getColumnIndex

    根据性能调优过程中的观察 cursor.getColumnIndex 的时间消耗跟 cursor.getInt 相差无几。可以在建表的时候用 static 变量记住某列的 index,直接调用相应 index 而不是每次查询。

  4. 异步线程

    Android 中数据不多时表查询可能耗时不多,不会导致 ANR,不过大于 100ms 时同样会让用户感觉到延时和卡顿,可以放在线程中运行,但 sqlite 在并发方面存在局限,多线程控制较麻烦,这时候可使用单线程池,在任务中执行 db 操作,通过 handler 返回结果和 UI 线程交互,既不会影响 UI 线程,同时也能防止并发带来的异常。

  5. SQLiteOpenHelper 维持一个单例

    因为 SQLite 对多线程的支持并不是很完善,如果两个线程同时操作数据库,因为数据库被另一个线程占用, 这种情况下会报“Database is locked” 的异常。所以在数据库管理类中使用单例模式,就可以保证无论在哪个线程中获取数据库对象,都是同一个。

    最好的方法是所有的数据库操作统一到同一个线程队列管理,而业务层使用缓存同步,这样可以完全避免多线程操作数据库导致的不同步和死锁问题。

  6. Application 中初始化

    • 使用 Application 的 Context 创建数据库,在 Application 生命周期结束时再关闭。
    • 在应用启动过程中最先初始化完数据库,避免进入应用后再初始化导致相关操作时间变长。
  7. 少用 AUTOINCREMENT

    主键加上 AUTOINCREMENT 后,可以保证主键严格递增,但并不能保证每次都加 1,因为在插入失败后,失败的行号不会被复用,会造成主键有间隔,继而使 INSERT 耗时 1 倍以上。

    这个 AUTOINCREMENT 关键词会增加 CPU,内存,磁盘空间和磁盘 I/O 的负担,所以 尽量不要用,除非必需。通常情况下都不是必需的。

事务

使用事务的两大好处是原子提交和更优性能:

主要三个方法:beginTransaction,setTransactionSuccessful,endTransaction。

SQLiteStatement

使用 Android 系统提供的 SQLiteStatement 来插入数据,在性能上有一定的提高,并且也解决了 SQL 注入的问题。

SQLiteStatement statement = dbOpenHelper.getWritableDatabase().compileStatement("INSERT INTO EMPERORS(name, dynasty, start_year) values(?,?,?)"); 
statement.clearBindings();
statement.bindString(1, "Max"); 
statement.bindString(2, "Luk"); 
statement.bindString(3, "1998"); 
statement.executeInsert();

SQLiteStatement 只能插入一个表中的数据,在插入前要清除上一次的数据。

索引

索引就像书本的目录,目录可以快速找到所在页数,数据库中索引可以帮助快速找到数据,而不用全表扫描,合适的索引可以大大提高数据库查询的效率。

优点:大大加快了数据库检索的速度,包括对单表查询、连表查询、分组查询、排序查询。经常是一到两个数量级的性能提升,且随着数据数量级增长。

缺点:

分类

  1. 直接创建索引和间接创建索引

    • 直接创建: 使用 sql 语句创建,Android 中可以在 SQLiteOpenHelper 的 onCreate 或是 onUpgrade 中直接 excuSql 创建语句,如 CREATE INDEX mycolumn_index ON mytable (myclumn)
    • 间接创建: 定义主键约束或者唯一性键约束,可以间接创建索引,主键默认为唯一索引。
  2. 普通索引和唯一性索引

    • 普通索引:CREATEINDEXmycolumn_indexONmytable(myclumn)
    • 唯一性索引:保证在索引列中的全部数据是唯一的,对聚簇索引和非聚簇索引都可以使用,语句为 CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)
  3. 单个索引和复合索引

    • 单个索引:索引建立语句中仅包含单个字段,如上面的普通索引和唯一性索引创建示例。
    • 复合索引:又叫组合索引,在索引建立语句中同时包含多个字段,如 CREATEINDEXname_indexONusername(firstname,lastname),其中 firstname 为前导列。
  4. 聚簇索引和非聚簇索引 (聚集索引,群集索引)

    • 聚簇索引:物理索引,与基表的物理顺序相同,数据值的顺序总是按照顺序排列,如 CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn) WITH ALLOW_DUP_ROW,其中 WITH ALLOW_DUP_ROW 表示允许有重复记录的聚簇索引
    • 非聚簇索引:CREATEUNCLUSTEREDINDEXmycolumn_cindexONmytable(mycolumn),索引默认为非聚簇索引

使用场景

  1. 当某字段数据更新频率较低,查询频率较高,经常有范围查询 (>, <, =,>=, <=)order bygroup by 发生时建议使用索引。并且选择度(一个字段中唯一值的数量 / 总的数量)越大,建索引越有优势
  2. 经常同时存取多列,且每列都含有重复值可考虑建立复合索引

使用规则

  1. 对于复合索引,把使用最频繁的列做为前导列 (索引中第一个字段)。如果查询时前导列不在查询条件中则该复合索引不会被使用。如 create unique index PK_GRADE_CLASS on student (grade, class)select * from student where class = 2 未使用到索引,select * from dept where grade = 3 使用到了索引
  2. 避免对索引列进行计算,对 where 子句列的任何计算如果不能被编译优化,都会导致查询时索引失效 select * from student where tochar(grade)=’2
  3. 比较值避免使用 NULL
  4. 多表查询时要注意是选择合适的表做为内表。连接条件要充份考虑带有索引的表、行数多的表,内外表的选择可由公式:外层表中的匹配行数 * 内层表中每一次查找的次数确定,乘积最小为最佳方案。实际多表操作在被实际执行前,查询优化器会根据连接条件,列出几组可能的连接方案并从中找出系统开销最小的最佳方案
  5. 查询列与索引列次序一致
  6. 用多表连接代替 EXISTS 子句
  7. 把过滤记录数最多的条件放在最前面
  8. 善于使用存储过程,它使 sql 变得更加灵活和高效 (Sqlite 不支持存储过程)

其它通用优化

上一篇下一篇

猜你喜欢

热点阅读