Android 应用性能优化总结

在移动端内存非常宝贵,我们要尽可能地去优化内存以达到最佳性能,并努力地去避免OOM问题。这篇文章总结一些最常见的Android性能优化的一些事项~


图片加载

在移动端,图片的加载往往是最消耗内存的,OOM也经常会发生在加载图片时。以下是加载图片的几个注意事项:

  • 按需要的大小加载。缩略图加载成原图的精细度并不会提升视觉上的效果,但会额外占用不少内存。
  • 图片的缓存使用LruCache,并且合理地设置缓存大小
  • 可以用一些高效的图片加载库,比如Facebook的Fresco

注意一些额外的内存消耗

  • 应特别注意HashMap这个数据结构,必要的时候可以用SparseArray等等的数据结构代替以节省内存
  • 需要频繁修改字符串的地方考虑用StringBuilder代替String
  • 避免创建无用的实例

有限度地使用Service

当我们启动一个Service时,系统会倾向于将这个Service所依赖的进程进行保留,这样就会导致这个进程变得非常消耗内存。并且,系统可以在LRU cache当中缓存的进程数量也会减少,导致切换应用程序的时候耗费更多性能。严重的话,甚至有可能会导致崩溃,因为系统在内存非常吃紧的时候可能已无法维护所有正在运行的Service所依赖的进程了。

因此,控制好Service的生命周期是很重要的。切忌一个无用的Service跑在后台,这会非常影响性能。如果一个逻辑只需要进行一次,那么可以用IntentService代替。

Handler必须为静态内部类

首先复习一下Java几种内部类的语言规范,Java中的inner class(内部类)定义在一个类的内部,它有一个指向外部类的引用。这就有可能导致一个问题,其外部类的生命周期已经结束,而由于inner class持有外部类的引用,并且inner class与其它对象还有引用关联,因此GC进行可达性分析时发现内部类与外部类仍有引用关系,因此不会cause GC,从而导致内存泄露。
static nested class(静态内部类)虽定义在类的内部,但是它不持有外部类的引用,只能访问外部类的静态实例和方法。

在安卓开发中,一个典型的例子是Handler的使用,它会有这样的问题:

In Android, Handler classes should be static or leaks might occur. Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class.

这句话的主旨就是,Handler类必须声明为静态(内部)类(static nested class),否则可能会发生内存泄露。因为,进入MessageQueueMessage会持有它们目标Handler的引用;这就可能出现一种情况,一个消息还在排队,而此时目标Handler类所对应的外部类的生命周期已到onDestroy,即将被GC,但由于目标Handler类仍持有其外部类的引用,使得外部类不能被回收,从而导致内存泄露。这一点说明了Handler类型的对象,生命周期是未知的

然而如果声明为static,又会有许多不方便的地方,比如很多逻辑需要访问外部类的非静态变量和非静态方法。此时,我们应该使用外部类的弱引用(WeakReference),举例表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static class MyHandler extends Handler {
private final WeakReference<DeviceUserFragment> mFragment;
public MyHandler(DeviceUserFragment fragment) {
mFragment = new WeakReference<>(fragment);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Object object = msg.obj;
switch (msg.what) {
case HWServiceConfig.GET_DEVICE_USER_INFO:
mFragment.get().mInfo = (DataUserInfo)object;
mFragment.get().isInfoOK = true;
mFragment.get().updateUI();
break;
default:
break;
}
}
}

这里顺便复习一下JVM中的四种引用的知识吧(直接扒的以前自己的文章):

  • StrongReference(强引用)是最普通的引用类型,只要强引用存在,GC就不会进行垃圾回收。
  • SoftReference(软引用)用来描述一些有用但是非必需的对象。如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,JVM就会把这个软引用加入到与之关联的引用队列中。软引用可用来实现内存敏感的高速缓存。
  • WeakReference(弱引用)是一种生命周期比软引用更短的引用。当GC扫描启动时,只要扫描到只具有弱引用的对象,无论内存是否够用都会执行GC;但由于GC线程优先级很低,因此并不一定能迅速发现这些弱引用对象。弱引用也可以和一个引用队列联合使用。
  • PhantomReference(虚引用)不同于其余三种引用,虚引用不会影响对象的生命周期,也无法通过虚引用获得对象的一个实例;如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动,它必须和引用队列联合使用。

明白了弱引用的生命周期,此处想必就很好理解了。构造一个外部类的弱引用,然后通过get方法获得其实例进行操作即可,既避免了内存泄露,又可以自由地调用外部类的方法。

WeakReferenceSoftReference 在其他很多地方也都有运用,以后再补充~


参考资料

文章目录
  1. 1. 图片加载
  2. 2. 注意一些额外的内存消耗
  3. 3. 有限度地使用Service
  4. 4. Handler必须为静态内部类
  5. 5. 参考资料