Android性能优化——I/O优化

Bitmap decode

BitmapFactory.java提供多个decode Bitmap的API,有decodeFile()、 decodeResource()、 decodeByteArray()、 decodeFileDescriptor()、 decodeStream()、 decodeResourceStream()。 而大家最常用的是decodeFile()。

Android 4.3 decodeFile()方法 最终使用的是BufferedInputStream,使用的Buffer大小为16KB。而4.4以后使用的是FileInputStream。
BufferedInputStream是字节缓冲流,BufferedInputStream比FileInputStream多了一个缓冲区,执行read时先从缓冲区读取,当缓冲区数据读完时再把缓冲区填满;

FileInputStream是字节流;使用BufferedInputStream读资源比FileInputStream读取资源的效率高(BufferedInputStream的read方法会读取尽可能多的字节),且FileInputStream对象的read方法会出现阻塞;

因此可以使用BufferedInputStream包裹输入流(FileInputStream)调用decodeStream(),来代替decodeFile()、decodeResource()方法。(大多数情况下差别不大)

随机读写的效率

  1. 当写操作在数据库的db文件和journal[1]文件中来回发生时,会导致随机读写。
  1. 当设置了AUTOINCREMENT的表中插入多条数据,那么每插入一条数据,都需要操作两张数据库表,这就意味着存在随机写。
  • 随机读写的缺点:

    1. 失去预读(read-ahead)的优化效果。
    2. 写入放大[2]

冗余读/写

  • SharedPreference apply
  • 需要在多个地方多次读取同一文件时(如配置文件),只读一次,把需要的内容写到缓存中
  • 优化sql语句减少无谓的查询(如 数据库中写入一条数据,若这条数据不存在,插入数据,若已存在,修改这条数据,这种情况下使用"insert or replace" 语句来避免先查询,后操作)。

主线程读写

  • 由于存在写入放大的情况,可能会让平时时间很短的操作被放大几十倍,因此 I/O 操作要在子线程中进行

    测试工具
    StrictMode Doc: https://developer.android.google.cn/reference/android/os/StrictMode.html

    • 作用
      主线程I/O 发现 + 定位 (也可以测主线程访问网络,将违规代码在log中定位)

    • 示例

    // 在Application 或Activity的onCreate中调用
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                  .detectDiskReads()
                  .detectDiskWrites()
                  .detectNetwork()   // or .detectAll() for all detectable problems
                  .penaltyLog()
                  .build());
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                  .detectLeakedSqlLiteObjects()
                  .detectLeakedClosableObjects()
                  .penaltyLog()
                  .penaltyDeath()
                  .build());
      
    

读写效率

  • ObjectOutputStream 读写序列化对象的优化
    ObjectOutputStream 在保存对象的时候,每个数据成员会带来一次 I/O 操作,即使很小的文件也会有成千上万次I/O的。
    如果在ObjectOutputStream上面再封装一个输出流ByteArrayOutputStream,先将对象序列 化后的信息写到缓存区中,然后再一次性地写到磁盘上,可减少磁盘操作。

  • 合理设置Buffer大小
    这里推荐使用Java默认的Buffer大小8KB;Buffer大小至少应为4KB。

    Buffer也不是越大越好,Buffer如果太大,会导致申请Buffer的时间变长,反而整体效率不高。

    还有一种更智能地确定Buffer大小的方法。这个方法由两个影响因子决定,一是Buffer size不能大于 文件大小;二是Buffer size 根据文件保存所挂载的目录的block size来确认Buffer大小,而数据库的pagesize,就是这样确定的(具体可见Android源码中SQLiteGlobal.java的getDefaultPageSize())。

数据库的打开与关闭

getWritableDatabase()方法的注释如下:

```Java
/**
 * Create and/or open a database that will be used for reading and writing.
 * The first time this is called, the database will be opened and
 * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
 * called.
 *
 * 

Once opened successfully, the database is cached, so you can * call this method every time you need to write to the database. * (Make sure to call {@link #close} when you no longer need the database.) * Errors such as bad permissions or a full disk may cause this method * to fail, but future attempts may succeed if the problem is fixed.

* *

Database upgrade may take a long time, you * should not call this method from the application main thread, including * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. * * @throws SQLiteException if the database cannot be opened for writing * @return a read/write database object valid until {@link #close} is called */ public SQLiteDatabase getWritableDatabase() { synchronized (this) { return getDatabaseLocked(true); } } ```

数据库打开以后,在真正不需要访问数据库以后再调用close关闭,避免重复打开,造成不必要的浪费。如果App中多处使用数据库,在应用程序退出时关闭即可。

AUTOINCREMENT的坑

The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. It is usually not needed.
In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID (except in WITHOUT ROWID tables) which is always a 64-bit signed integer.
On an INSERT, if the ROWID or INTEGER PRIMARY KEY column is not explicitly given a value, then it will be filled automatically with an unused integer, usually one more than the largest ROWID currently in use. This is true regardless of whether or not the AUTOINCREMENT keyword is used.
If the AUTOINCREMENT keyword appears after INTEGER PRIMARY KEY, that changes the automatic ROWID assignment algorithm to prevent the reuse of ROWIDs over the lifetime of the database. In other words, the purpose of AUTOINCREMENT is to prevent the reuse of ROWIDs from previously deleted rows.

—— SQLite官方文档

�AUTOINCREMENT可以保证主键的严格递增,但使用AUTOINCREMENT会增加额外的开销,INSERT数据时耗时 1倍以上,所以非必需时尽量不要用。


  1. 文件.db_journal 是sqlite的一个临时的日志文件,主要用于sqlite事务回滚机制,在事务开始时产生。在事务结束时删除。当程序发生崩溃或者系统断电时该文件将留在磁盘上,以便下次程序运行时进行事务回滚。 ↩

  2. 写入放大(英语:Writeamplification,简称WA)是闪存和固态硬盘(SSD)中一种不期望的现象,即实际写入的物理信息量是将要写入的逻辑数量的多倍。因为闪存在可重新写入数据前必须先擦除,执行这些操作的过程就产生了一次以上的用户数据和元数据的移动(或重新写入)。此倍增效应会增加请求写入的次数,这会缩短SSD的寿命,从而减小SSD可靠运行的时间。增加的写入也会消耗闪存的带宽,这个效应主要会降低SSD的随机写入性能。许多因素会影响SSD的写入放大;一些可以由用户来控制,而另一些则是数据写入和SSD使用的直接结果。 ↩

你可能感兴趣的:(Android性能优化——I/O优化)