Android应用中导致内存泄露大多数是由于相同的错误:长时间持有Context的引用。
在Android中,Context用于很多操作但主要用于加载和访问资源。这是为什么所有组件会在构造函数中接收Context参数。在常规的Android应用中,通常有Activity和Application两种Context。通常使用第一种传递到需要Context的类和方法:
1 | @Override |
这意味着Views持有整个Activity的引用,因此包括Activity整个View层级和它所有的资源。如果你泄露了Context(泄露意意味着你持有它的引用从而阻止GC回收它),会导致大量的内存泄露。如果你不注意的话会很容易导致Activity的泄露。
当手机屏幕方向改变,默认为销毁当前Activity保存它的状态并创建一个新的。这样Android将重新从资源中加载应用的UI。假设你写了一个应用包含一个很大的bitmap,你不想每次旋转时加载它。这有一个很容易的方式把保存它在静态域中:
1 | private static Drawable sBackground; |
这段代码很简单但严重错误,它泄露了屏幕方向改变时第一次创建的Activity。当Drawable attached到View上,view在Drawable上设置作为回调。在上面的代码片段中,Drawable持有TextView的引用而TextView持有Activity的引用,这样就会导致有相当多的引用(取决于你的代码)。
这个例子是泄露Context的一种情况,你可以看看Home屏幕的源码(找到unbindDrawables()方法)是怎样处理的,当Activity被销毁存储Drawable的回调设置为null。如果你很有兴趣,可以创建一个泄露Context链,这会使你很快得到内存溢出的错误。
这有两种简单的方法避免Context相关的内存泄露。最明显的一种是避免在Context的范围之外使用。上面的例子说明的是静态引用的情况,但内部类和它隐式引用外部类同样危险。第二种解决方案是使用Application Context。只要应用存活这个Context就会存在并且不依赖于Activity的生命周期。如果长时间存活的对象需要Context,记住使用Application对象。你可以很容易的得到它通过调用Context.getApplicationContext() 或 Activity.getApplication()。
总之,为了避免Context相关的内存泄露,请记住下面:
- 不要长时间持有Context-Activity的引用
- 尝试使用Application Context而不是Activity Context
- 如果你不控制内部类的生命周期,避免在Activity中使用非静态内部类,应该使用静态内部类并对Activity作弱引用(WeakReference)处理
- 垃圾回收器无法处理内存泄露