原文来自Android SDK文档中的 docs/resources/articles/avoiding-memory-leaks.html
android应用,堆内存大小限制为16MB(至少在T-Mobile G1上是这样)。 对手机而言, 这已经是相当大一部分内存,但对某些开发者而言16MB太少了。 就算不准备使用全部的16MB内存, 至少也应用使用尽可能少的内存,从而避免其他应用因为内存不足而被杀死。Android平台上, 内存中的应用越多, 用户切换应用的速度就可以越快。在我的工作中, 我遇到过Android应用内存泄漏问题大部分可以归结为同样一个错误: 长时间持久Context对象的引用。
Android平台上, Context用作大量的操作, 但总的来说主要是用于加载和访问资源。这也正是所有的Widget的构建方法需要一个Context参数的原因。 对普通Android应用来说, 有两种Contxt:Activity Context和Application Context。 通常会将前者传递给需要Context的类或方法。
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
setContentView(label);
}
这意味着这些View持有当前Activity的引用, 也间接地指向了当前Activity所持有的引用。 这包括整个View树结构及其使用的资源。所以, 如果Context泄漏(泄漏即你引用这个Context,导致GC不能回收它), 会引用大量内存的泄漏。如果不注意, 很容易泄漏整个Activity使用的内存。
当改变屏幕方向时,缺省情况下系统会销毁当前Activity(会保存其状态),然后重新生成一个Activity实例。这个过程中, Android会重新加载应用的UI资源。想象一下, 你写了一个使用了大图片的应用, 你不想每当改变屏幕方向时就要重新加载这张大图片。 (注:显然加载大图片会非常耗时)。 最简单的办法就是用一个静态成员变量保存这张图片的引用,避免每次转动屏幕时重新加载:
@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);
}
这段代码速度快多了, 但也非常错误! 第一次改变屏幕方向时, 最初创建的Activity泄漏了!当Drawable被附着到View上面, 这个View会作为Drawable的一个回调接口。 在上面的代码中, sBackground持有label的引用, 而label又持有Activity(即Activity Context)的引用, 从而持有非常多的引用(与具体代码相关)。
上面的例子是Context相关的内存泄漏最简单一种场景。 可以在Home screen's source code 中找到解决办法(见 unbindDrawables()方法): 当Activity被销毁时, 将保存起来的drawable的回调接口置为null。 有趣的是, 某些场景下会出现Context泄漏链(原文a chain of leaked contexts), 相当糟糕。 这些场景中内存会很快耗尽。 (注意, Context泄漏链没那么有趣, 但也不算太糟糕。 内存很快耗尽意味着问题容易暴露和复现)。
有两个简单的方法避免Context相关的内存泄漏。 最直接办法是避免在Context对象逃逸到的其作用域(或生命周期)外。 上面的例子中展示了静态引用是如何引发问题的,实际上内部类隐含持有外部对象的引用, 同样非常危险。 另外一个办法就是使用Application Context. 这种Context生命周期与应用所在进程的生命周期一样长, 而不依赖于Activity的生命周期无关。 当需要持有一个长生命周期的、而且使用Context的对象时, 请使用这个Application Context。 可以通过调用Context.getApplicationContext()或Activity.getApplication()来获取这个Context。
总之, 想要避免Context相关的内存泄漏, 请记住以下几点:
- 不要长期持有Activity(Context)的引用。 对Activity的引用, 其生命周期应当与Actvity本身的生命周期相同。
- 尽量使用Application Context而不是Activity Context
- 如果不能控制它们的生命周期, 则要避免在Activity中使用非静态的内部类(原文non-static inner classes)。 可以使用一个嵌套类(原文static inner classes), 并且持有对外部Activity对象的弱引用(weak reference)。 这种方法可参考ViewRoot及其嵌套类W
- 垃圾回收器并不是保证避免内存泄漏