Android 内存泄漏

jopen 9年前

原文:http://android-developers.blogspot.jp/2009/01/avoiding-memory-leaks.html


Andriod应用的堆栈大小在T-Mobile G1上被限制在16MB,对于一个手机这已经是很大的内存了,但对一些开发者来说还是不够。即使你不需要使用这么多的内存空间,你也应该尽可能地节省使用,这样就不至于在其他应用运行的时候将你的杀掉。Android可以在内存中缓存的应用越多,应用间的切换也就越流畅。作为我的一部分工作,我深入了解了Android应用内存泄漏问题,发现大多数都是同一类情况——长时间保持对一个Context的引用(keep a long_lived reference to a Context)。

在 Android里,许多操作都需要使用Context,但绝大部分情况是获取、加载资源,这也是所有的控件需要在构造函数里接收一个Context参数的原因。一般情况下,我们可以获取两种Context,Activity和Application,开发者通常会将第一个也就是Activity作为参数进行传递。

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

这意味着view持有了整个Activity的引用和他里面的所有东西,比如整个view hierarchy和所有的资源。因此,如果你泄漏了Context(“泄漏”的意思是你保持了他的引用,使得GC不能及时回收),你就泄漏了一大部分内存。如果不够小心点话泄漏整个activity是很容易的。

当屏幕方向改变时系统会默认destroy当前的activity,保存当前状态并创建一个新的activity,也就是Android会重新从资源文件中加载应用的视图。想象一下你的应用里有一幅大图,但并不像每次旋转屏幕都重新去加载,将它缓存在内存里最简单的办法是将它声明为static类型:

</div> </div>
    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);        }  

上面的代码运行很快,但做法却是错误的,当屏幕旋转的时候泄漏了整个activity,当一个Drawable关联到view上,这个view就被设置为 drawable的一个callback,在上面的代码里,这意味着drawable保持了TextView的引用,而TextView又保持了整个 Activity的引用,也就几乎关联了activity里的所有东西。 这个例子是Context泄漏里最简单的一种情况,你可以在HomeScreen的源码里看我们是怎么通过把drawable的callback设置为 null来解决这个问题的(搜索unbindDrawables()方法),有趣的是,有时会创建出一条泄漏context的调用链,这样会使你的内存更快的耗尽。
有两种简单的方式来避免context相关的内存泄漏,一个是避免context逃出他自己的范围(avoid escaping the context outside of its own scope),上面的例子展示了静态引用造成泄漏的情况,但是内部类对外部类的强引用也同样危险。另一种解决方法是使用Application context,这个context会一直存在,直到你的应用退出,而不会依赖于activity的生命周期。如果你有一个长时间存活的对象,而且对象有 context的引用,记住使用application context,这个你可以很容易地通过Context.getApplicationContext()或者 Activity.getApplication()来获取。
总之,避免context相关的内存泄漏,记得下面几条
  • 不要保持长时间对activity context类型的引用(一个对activity的引用应该和这个activity有相同的生命周期)
  • 使用application context而不是activity context
  • 避免在一个activity中使用非静态(non-static)内部类,如果你不管他的生命周期的话,使用一个静态内部类,然后在这个类内部维持一个对activity的弱引用,就像在ViewRoot类中的W内部类这样。
  • 垃圾回收不保证内存泄漏