(翻译)avoiding memory leaks

原文来自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
  • 垃圾回收器并不是保证避免内存泄漏

 

你可能感兴趣的:(memory leak)