Android移动性能实战笔记

一、磁盘

1.如果需要频繁读某个文件,请做缓存操作。

2.SharePreferences的commit对应一次文件的打开与关闭。

另外,apply是异步操作,commit为同步操作。

3.主线程I/O与子线程I/O之争。

  • 若I/O操作放在主线程,将会影响UI的绘制。
  • 若I/O操作放在子线程,虽然不影响UI线程,但由于子线程优先级不高,I/O耗时会变长。

4.对象序列化最佳实践。

  • 序列化写:BufferedOutputStream/ByteArrayOutputStream + ObjectOutputStream
  • 序列化读:BufferedInputStream/ByteArrayInputStream + ObjectInputStream
  • 原理:ObjectOutputStream在序列化的时候,会把内存中的每个对象保存到磁盘中,在保存对象时,每个数据成员都会带来一次I/O操作。使用缓冲区,可有效减少磁盘I/O次数。比如3K的大对象,使用2K的缓冲区,两次I/O即可。

5.使用Buffer太小或太小,也会影响I/O效率。

  • 在读写时候使用缓冲区可以减少读写次数,从而减少了切换内核态的次数,提高读写效率。
  • Buffer也不是越大越好,因为Buffer过大,申请时间会变长。
  • 经验值8KB = 8192byte。

6.解压文件的最佳实践。

结论:

  • 如果Zip文件已在磁盘(已下载),且解压所有Zip中所有文件,建议使用ZipFile,效率较ZipInputStream提升15%-27%。
  • 仅解压Zip某些文件,建议使用ZipFile,因为ZipFile有RandomAccessFile特性。
  • 如果Zip不在磁盘上,或顺序解压一小部分文件,或Zip文件目录遭到损害,建议使用ZipInputStream。

最佳实践代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-----------------------------ZipInputStream-----------------------------
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
BufferedInputStream bis = new BufferedInputStream(zis);
byte[] buffer = new byte[8192];
while ((ze = zis.getNextEntry()) != null) {
while ((count = bis.read(buffer)) != -1) {
fos.write(buffer, 0, count);
}
}
--------------------------------ZipFile---------------------------------
Enumeration e = ZipFile.entries();
while (e.hasMoreElements()) {
entry = (ZipEntry) e.nextElement();
if (低压缩率文件,如文本) {
is = new BufferedInputStream(zipFile.getInputStream(entry));
} else if (高压缩文件,如图片) {
is = zipFile.getInputStream(entry);
byte[] buffer = new byte[8192];
while ((count = is.read(buffer, 0, buffer.length)) != -1) {
fos.write(buffer, 0, count);
}
}
}

7.请勿重复打开数据库。

由SQLiteDatabase的#getWriteableDatabase()方法的注释:一旦打开数据库,该链接会被缓存,以供下一次使用,只有当真正不需要时,调用close()关闭。

8.减少使用select *。

9.请勿滥用AUTOINCREMENT关键字。

AUTOINCREMENT用于数据库主键唯一性、递增性,会增加CPU、内存、磁盘空间和磁盘I/O的负担,所以尽量不要用。因为AUTOINCREMENT实现原理是维护另一张表,表记录了已使用的最大行号等信息。

二、内存

获取虚拟机分配的内存大小:

1
long maxMemory = Runtime.getRuntime().maxMemory();

那么什么是管理好内存呢?

  • 减少内存的申请回收。
  • 减少常驻内存。
  • 避免内存泄漏。

实用工具:

1.内类是危险的编码方式

java的非静态类对象会持有外部类的强引用。在Android中,某些类是具有特殊的生命周期的,比如Context、Fragment。一旦他们与非静态类对象生命周期不同步,就会出现内存泄漏问题,甚至是crash。

解决方案:

  • 尽量不用非静态内部类。
  • 若需要外部类引用,请使用弱引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class WeakContainer<T> {
private WeakReference<T> mContainer;
public WeakContainer(T ref) {
mContainer = new WeakReference<>(ref);
}
public T getRef() {
return mContainer.get();
}
}
使用时继承它实现需要的接口就可以了。

2.适当选择图片解码方式

头像、缩略图等小图,如果没必要高清显示,那么可以选择RGB_565。

  • RGB_565 每个像素使用2字节16位保存颜色信息,其中红色5位,绿色6位,蓝色5位。
  • Android默认使用ARGB_8888,包含透明通道,共4字节32位。

3.大图裁剪压缩

大图解码往往是OOM的元凶。

4.提高Bitmap的缓存命中率

比如社交类产品一般有默认头像,url一般均为同一个,在做图片缓存时,应使用url作为key。

三、网络

1.业务成功率

  • 弱网
  • 网络拥塞

2.业务网络延时

  • DNS解析
  • 长连接
  • 短连接
  • 接收窗口

3.业务带宽成本

  • 图片、大数据压缩
  • 数据增量,比如好友列表应采用增量更新,需要制定协议
  • 资源去重,主要是压缩包和非压缩包之间去重

4.总结

  • 避免重复上传下载
  • JS/CSS/HTML需要压缩
  • 图片压缩
  • 定时网络请求尽量合并在一个时间
  • 网络重试必须有明显结束条件
  • 流量兜底能力,避免流量偷跑,服务端控制终止数据传输。

四、CPU

1.争夺CPU资源

  • 计算密集型
  • I/O密集型

2.建议

  • 能用int不要用float
  • 容器类选择合适的,比如ArrayMap,SparseArray,ConcurrentHashMap
  • 容器类如果可以,请构建的时候设置它的容量,避免不必要的扩容
  • 根据CPU核心数选择合适线程数
  • 优化算法

五、电量

针对CPU唤醒优化。

  • 锁屏,灭屏,后台停止耗电操作:动画、SurfaceView绘制。
  • 锁屏,灭屏正确释放WakeLock,否则导致CPU无法休眠。