如何避免Android 内存泄露

来源:互联网 发布:斯大林格勒战役 知乎 编辑:程序博客网 时间:2024/05/16 08:56

Android的应用程序,至少在T-Mobile的G1 ,限制为16 MB堆。这对手机来说是很多的内存,然而对开发者来说却很少。即使你不想使用这么多内存,但是你应该尽可能少的使用这些内存。越多的应用程序常驻内存,在应用程序之间切换越快。由于我工作的一部分,我遇到了内存泄漏的Android应用程序的问题,他们大部分时间都因同样的错误:保持长寿命引用上下文。

Android applications are, at least on the T-Mobile G1, limited to 16 MB of heap. It's both a lot of memory for a phone and yet very little for what some developers want to achieve. Even if you do not plan on using all of this memory, you should use as little as possible to let other applications run without getting them killed. The more applications Android can keep in memory, the faster it will be for the user to switch between his apps. As part of my job, I ran into memory leaks issues in Android applications and they are most of the time due to the same mistake: keeping a long-lived reference to a Context.

在Android上下文是用于多种业务,但大多是来加载和访问资源。这就是为什么所有的部件会在其构造函数中接收上下文参数。你通常会使用两种Context,Activity 和Application.这通常是第一种情况,开发者传递Context给需要的类或者方法。

On Android, a Context is used for many operations but mostly to load and access resources. This is why all the widgets receive a Context parameter in their constructor. In a regular Android application, you usually have two kinds of Context,Activity and Application. It's usually the first one that the developer passes to classes and methods that need a Context:

@Overrideprotected void onCreate(Bundle state) {  super.onCreate(state);    TextView label = new TextView(this);  label.setText("Leaks are bad");    setContentView(label);}

这意味着这个View 获得了这个Activity的引用,因此任何你的Activity抓着:通常是整个视图与资源。因此如果你泄露这个Context(也就是说因为你有这个Context的引用,组织GC回收它),你泄露了整个内存。如果你不小心,泄露整个Activity非常容易。

This means that views have a reference to the entire activity and therefore to anything your activity is holding onto; usually the entire View hierarchy and all its resources. Therefore, if you leak the Context ("leak" meaning you keep a reference to it thus preventing the GC from collecting it), you leak a lot of memory. Leaking an entire activity can be really easy if you're not careful.

当屏幕旋转是系统会默认销毁当前的Activity,创建一个新的Activity.这样做,Android将重新加载应用程序的UI从资源.想象一下,你开发了一个使用很大的位图资源的APP,但是你不想旋转的时候重新加载整个位图。最简单的方法是旋转的时候不必重新加载它,使他为static 属性。

When the screen orientation changes the system will, by default, destroy the current activity and create a new one while preserving its state. In doing so, Android will reload the application's UI from the resources. Now imagine you wrote an application with a large bitmap that you don't want to load on every rotation. The easiest way to keep it around and not having to reload it on every rotation is to keep in a static field:

private static Drawable sBackground;@Overrideprotected 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,该视图被设置它的回调。在上面的代码段,这意味着绘制有一个参考的TextView其本身具有Activity的引用。

This code is very fast and also very wrong; it leaks the first activity created upon the first screen orientation change. When aDrawable is attached to a view, the view is set as a callback on the drawable. In the code snippet above, this means the drawable has a reference to the TextView which itself has a reference to the activity (the Context) which in turns has references to pretty much anything (depending on your code.)

这是一个最简单的关于Context泄露的例子。你可以在Activity销毁时将Drawable的回调设置为空解决这个问题。当创建一个具备内存泄露的环境是不应该的。

This example is one of the simplest cases of leaking the Context and you can see how we worked around it in the Home screen's source code (look for the unbindDrawables() method) by setting the stored drawables' callbacks to null when the activity is destroyed. Interestingly enough, there are cases where you can create a chain of leaked contexts, and they are bad. They make you run out of memory rather quickly.

这里有两个方法来避免这个问题,最明显的是避免Context在其作用域之外。这里举了一个静态引用计数的例子,但是内部类隐式引用外部类问题很难避免。第二种方法是使用Application context.这个context的生命周期与APP一样长,但是不依赖Activity的生命周期。如果你计划使用长寿命对象,并且需要Context,你可以通过callingContext.getApplicationContext() or Activity.getApplication()轻松的得到它.

There are two easy ways to avoid context-related memory leaks. The most obvious one is to avoid escaping the context outside of its own scope. The example above showed the case of a static reference but inner classes and their implicit reference to the outer class can be equally dangerous. The second solution is to use the Application context. This context will live as long as your application is alive and does not depend on the activities life cycle. If you plan on keeping long-lived objects that need a context, remember the application object. You can obtain it easily by callingContext.getApplicationContext() or Activity.getApplication().

总之,为了避免Context相关的泄露,注意如下规则:

In summary, to avoid context-related memory leaks, remember the following:

不要使用长寿命对象引用Context

ontext-application 替换 context-activity

如果你不控制他们的生命周期,避免使用非静态的内部类。使用静态内部类弱引用Actity.

垃圾回收不能保证内存不泄漏

  • Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
  • Try using the context-application instead of a context-activity
  • Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a WeakReferenceto the outer class, as done in ViewRoot and its W inner class for instance
  • A garbage collector is not an insurance against memory leaks
0 0