Android内存储器泄露之Context引用

Android内存泄露之Context引用

以下是对Android SDK 一篇文章的简单翻译,原文地址为:

http://developer.android.com/resources/articles/avoiding-memory-leaks.html

 

Andriod应用程序是运行在linux上的dalvik虚拟机,其可分配的堆内存有一个限制,如T-moblie G1为16MB。对手机来说,16MB算是很多了,但对一些开发者来说是太少了。因此,尽管你可能不打算用光这些内存,你也应该尽量用最少的内存,以让其它应用程序不因为系统内存过低而被系统给强制杀掉。在Android里,越多的应用可以在内存里保持,对用户来说,就可以更快地在各个应用之间切换。

 

Android应用里一个最常见的内存泄露例子是: 保持对一个Context对象的长引用(Keeping a long-lived reference to a Context)。

 

在Android里,一个Context对象可以用来做很多操作,但最常见的是用来载入和访问资源。这也是所有Widget对象都要在其构造函数里接收一个Context参数的原因。在一个常规Android应用里,通常有两种Context, 即 Activity 和 Application。通常,开发者会传Activity对象到需要Context的类和方法。

 

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);
  
  TextView label = new TextView(this);
  label.setText("Leaks are bad");
  
  setContentView(label);
}

 

这意味着views有一个指向整个activity的引用,同时可以引用到activity所持有的任何资源,比如整个view层次结构和它所有的资源。因此,如果泄露了Context对象(泄露指持有一个指向Context对象的引用而阻止内存垃圾回收器GC来回收它),将会泄露很多内存。 如果不小心的话,泄露整个activity对象真是很容易的事。

 

当屏幕横竖方向切换时,系统默认会摧毁当前activity,然后创建一个新的activity,当然之前的activity状态会保存。在这个过程中,Android会重新载入应用UI资源。 因此,假如你写了一个应用,显示一个很大的bitmap图像,你不想在每次屏幕横竖方向切换时都重新载入图像一次,最容易的方式是将图像存在一个静态成员里:

 

private static Drawable sBackground;
  
@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);
  
  TextView label = new TextView(this);
  label.setText("Leaks are bad");
  
  if (sBackground == null) {
    sBackground = getDrawable(R.drawable.large_bitmap);
  }
  label.setBackgroundDrawable(sBackground);
  
  setContentView(label);
}
 

以上代码确实运行很快,但是非常错误的,其泄露了在第一次屏幕方向切换前创建的activiy对象。当一个Drawable对象连接到一个view时,view会被设置为Drawable对象的callback。对以上代码,drawable对象将有一个指向TextView的引用,而TextView自身有一个指向activity(Context)的引用,而activity将引用到非常多的其它资源(取决于你的代码实现)。

 

此示例是一种最简单的泄露Context对象的案例。 你可以在 Home screen's source code (查找unbindDrawables方法)看到我们是如何规避此问题的。

即:当activity被摧毁时,设置保存的drawable对象的callback为null。

有意思的是,有一些情况你可能会创建一系列的Context泄露,这是相当糟糕的,会使你很快地用光内存。

 

如何避免呢? 有两种简单的方式来避免Context相关的内存泄露。最明显的一种是避免将Context对象传出其自身的作用域。上面的示例显示了静态引用的方式,其实内部类及其隐式地引用外部类也是同样危险的。第二种方式是使用Application context对象。 Application context将会一直存在,只要应用程序在存活状态,且不依赖于activity的生命周期。 如果你打算保持一个需要context的长期存活的对象,那就使用application对象。其可以很容易地通过 Context.getApplicationContext() or Activity.getApplication() 得到。

 

总之, 要避免Context相关的内存泄露,牢记以下几点:

 

1. 不要保存一个指向activity context的长期存活的引用(指向一个acitivy的引用应该和该activity拥有同样的生命周期)。

 

2.  使用application context对象来代替acitivity context对象。

 

3.  如果不能控制activity里的非静态内部类的生命周期的话,避免使用之。 尽量使用静态内部类,在其内部持有指向activity的弱引用(weak reference)。如ViewRoot及其W内部类示例。

 

4. 垃圾内存回收器并不能保证防止内存泄露。