本帖最后由 Andy.Ma 于 2013-3-23 23:11 编辑
在开发Android App中,Context几乎无处不在。如果用不好Context很可能就会引起内存泄露。
在Android中,Context用于许多操作,但最主要是加载和访问资源。这就是Android所有的UI控件的构造方法中需要接收一个Context参数的原因。在常规的Android App中,通常主要有两种类型的Context,分别是Activity和Application。
我们看一段代码片段:
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks test");
setContentView(label);
}
上面代码中的向label传入了一个this,意味着label持有了整个Activity。也就是说持有了整个该Activity的View hierarchy和资源。所以如果泄露了Context,将会泄露很多内存。如果你不小心,泄露整个Activity是很容易出现的。
注意,这里所讲的Context泄露是指:一个对象保持并引用了一个Context,使得GC无法回收它。
默认的,当屏幕方向改变时,当前的Activity将会销毁并且会创建一个新的Activity,新创建的Activity将会保持之前的状态。在这种情况下,Android系统会从资源中从新加载UI。假设你正在编写一个Activity,该Activty将加载一个大的位图( a large bitmap),但是你不想每次屏幕方向改变时都加载它。最简单的办法是创建一个静态的成员变量来引用它,看下面代码片段:
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks test");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
以上代码执行会很有效率,但是存在很大一个问题:当屏幕方向改变时,首次创建的Activity将会被泄露。当一个Drawable依附在View上时,Drawable会使用setCallback保留对该View的引用。从上面代码可以看出:sBackground 将会引用label,而label又持有整个Activity(Context)资源。也就是说当屏幕改变方向时,sBackground不被回收,他引用的一系列资源也就回收不掉。
以上代码只是一个Context泄露的例子,如果想解决上面这个泄露的问题,就需要在Activity的onDestory方法中调用sBackground.setCallback(null),来解除sBackground 对label的引用。
有两种方法可以避免Context相关的内存泄露。最明显的一种方法是:避免context逃离自己的范围。上面的例子是静态引用Context,同样的静态引用非静态的内部类也同样危险,因为非静态内部类隐式的引用外部类。
第二种方法就是用Application Context,Application Context对于整个应用APP来说是全局的,它不依靠Activity的生命周期。如果你需要保持一个长期的对象并且需要Context,记住要用Application Context.通过调用Context.getApplicationContext()和 Activity.getApplication(),你可以很方便的获得Application Context。
总之,为了避免Context相关的内存泄漏,请记住以下几点:
(1).不要保持一个长期的Activity的Context引用。一个Activity的引用必须与该Activity的生命周期相同。
(2).试着用Application Context来代替Activity Context。
(3).如果你不控制非静态的内部类的生命周期,请不要在Activity中定义非静态的内部类。可以使用静态内部类,并且使用弱引用( WeakReference)来引用Activity(Context).
(4).垃圾回收器(GC)不能阻止内存泄露。
|