[转]How to Leak a Context: Handlers & Inner Classes

Consider the following code:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }
}

While not readily obvious, this code can cause cause a massive memory leak. Android Lint will give the following warning:

In Android, Handler classes should be static or leaks might occur.

But where exactly is the leak and how might it happen? Let‘s determine the source of the problem by first documenting what we know:

  1. When an Android application first starts, the framework creates a Looper object for the application‘s main thread. A Looper implements a simple message queue, processingMessage objects in a loop one after another. All major application framework events (such as Activity lifecycle method calls, button clicks, etc.) are contained inside Messageobjects, which are added to the Looper‘s message queue and are processed one-by-one. The main thread‘s Looper exists throughout the application‘s lifecycle.
  2. When a Handler is instantiated on the main thread, it is associated with the Looper‘s message queue. Messages posted to the message queue will hold a reference to theHandler so that the framework can call Handler#handleMessage(Message) when the Looper eventually processes the message.
  3. In Java, non-static inner and anonymous classes hold an implicit reference to their outer class. Static inner classes, on the other hand, do not.

So where exactly is the memory leak? It‘s very subtle, but consider the following code as an example:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { }
    }, 60 * 10 * 1000);

    // Go back to the previous Activity.
    finish();
  }
}

When the activity is finished, the delayed message will continue to live in the main thread‘s message queue for 10 minutes before it is processed. The message holds a reference to the activity‘s Handler, and the Handler holds an implicit reference to its outer class (theSampleActivity, in this case). This reference will persist until the message is processed, thus preventing the activity context from being garbage collected and leaking all of the application‘s resources. Note that the same is true with the anonymous Runnable class on line 15. Non-static instances of anonymous classes hold an implicit reference to their outer class, so the context will be leaked.

To fix the problem, subclass the Handler in a new file or use a static inner class instead. Static inner classes do not hold an implicit reference to their outer class, so the activity will not be leaked. If you need to invoke the outer activity‘s methods from within the Handler, have the Handler hold a WeakReference to the activity so you don‘t accidentally leak a context. To fix the memory leak that occurs when we instantiate the anonymous Runnable class, we make the variable a static field of the class (since static instances of anonymous classes do not hold an implicit reference to their outer class):

public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }

  private final MyHandler mHandler = new MyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

    // Go back to the previous Activity.
    finish();
  }
}

The difference between static and non-static inner classes is subtle, but is something every Android developer should understand. What‘s the bottom line? Avoid using non-static inner classes in an activity if instances of the inner class outlive the activity‘s lifecycle. Instead, prefer static inner classes and hold a weak reference to the activity inside.

As always, leave a comment if you have any questions and don‘t forget to +1 this blog in the top right corner! :)

【译】什么导致了Context泄露:Handler&内部类

思考下面代码

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }
}

如果没有仔细观察,上面的代码可能导致严重的内存泄露。Android Lint会给出下面的警告:

In Android, Handler classes should be static or leaks might occur.

但是到底是泄漏,如何发生的?让我们确定问题的根源,先写下我们所知道的
1、当一个Android应用程序第一次启动时,Android框架为应用程序的主线程创建一个Looper对象。一个Looper实现了一个简单的消息队列,在一个循环中处理Message对象。所有主要的应用程序框架事件(如活动生命周期方法调用,单击按钮,等等)都包含在Message对象,它被添加到Looper的消息队列然后一个个被处理。主线程的Looper在应用程序的整个生命周期中存在。
2、当一个Handle在主线程被实例化,它就被关联到Looper的消息队列。被发送到消息队列的消息会持有一个Handler的引用,以便Android框架可以在Looper最终处理这个消息的时候,调用Handler#handleMessage(Message)
3、在Java中,非静态的内部类和匿名类会隐式地持有一个他们外部类的引用。静态内部类则不会。

那么,到底是内存泄漏?好像很难懂,让我们以下面的代码作为一个例子

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // 延时10分钟发送一个消息
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { }
    }, 60 * 10 * 1000);

    // 返回前一个Activity
    finish();
  }
}

当这个Activity被finished后,延时发送的消息会继续在主线程的消息队列中存活10分钟,直到他们被处理。这个消息持有这个Activity的Handler引用,这个Handler有隐式地持有他的外部类(在这个例子中是SampleActivity)。直到消息被处理前,这个引用都不会被释放。因此Activity不会被垃圾回收机制回收,泄露他所持有的应用程序资源。注意,第15行的匿名Runnable类也一样。匿名类的非静态实例持有一个隐式的外部类引用,因此context将被泄露。

为了解决这个问题,Handler的子类应该定义在一个新文件中或使用静态内部类。静态内部类不会隐式持有外部类的引用。所以不会导致它的Activity泄露。如果你需要在Handle内部调用外部Activity的方法,那么让Handler持有一个Activity的弱引用(WeakReference)以便你不会意外导致context泄露。为了解决我们实例化匿名Runnable类可能导致的内存泄露,我们将用一个静态变量来引用他(因为匿名类的静态实例不会隐式持有他们外部类的引用)。

public class SampleActivity extends Activity {
    /**
    * 匿名类的静态实例不会隐式持有他们外部类的引用
    */
    private static final Runnable sRunnable = new Runnable() {
            @Override
            public void run() {
            }
        };

    private final MyHandler mHandler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 延时10分钟发送一个消息.
        mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

        // 返回前一个Activity
        finish();
    }

    /**
    * 静态内部类的实例不会隐式持有他们外部类的引用。
    */
    private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;

        public MyHandler(SampleActivity activity) {
            mActivity = new WeakReference<SampleActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            SampleActivity activity = mActivity.get();

            if (activity != null) {
                // ...
            }
        }
    }
}

静态和非静态内部类的区别是比较难懂的,但每一个Android开发人员都应该了解。开发中不能碰的雷区是什么?不在一个Activity中使用非静态内部类, 以防它的生命周期比Activity长。相反,尽量使用持有Activity弱引用的静态内部类。

时间: 2024-10-09 08:19:36

[转]How to Leak a Context: Handlers & Inner Classes的相关文章

How to Leak a Context: Handlers & Inner Classes

Consider the following code: public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } } While not readily obvious, this code can cause cause a m

Android中Handler引起的内存泄露

在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.通常我们的代码会这样实现. 但是,其实上面的代码可能导致内存泄露,当你使用Android lint工具的话,会得到这样的警告 In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their t

Android中使用Handler引发的内存泄露

转载请注明出处:http://blog.csdn.net/allen315410/article/details/43638373 本文翻译自:国外某位开发者的博客How to Leak a Context: Handlers & Inner Classes,英文可以的朋友可以直接点击原文查看. 在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.通常我们的代码会这样实现. public class SampleActivity extends Activity

【翻译】Android避免内存泄露(Activity的context 与Context.getApplicationContext)

原谅地址:http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html ,英文原文在翻译之后 Android 应用至少,在T-Mobile G1这个型号,就有16MB的堆内存.这个容量对于手机来说是很大了,但是对于有些开发者来说是少了些.为了全其他应用可以运行而不被系统杀掉,即使你没有打算使用完所有分配的容量,你也应该尽量少地使用这些容量. Android能保存越多的应用,用户在切换应用的时候就会越快.我在工作

Servlet Config和Context入门

接下来学习一下ServletConfig和ServletContext的内容,简单来说ServletConfig是相对当前servlet的,ServletContext是相对整个web应用的,此外ServletContext还可以获得资源路径,下面简单整理一下. Servlet Config 获取当前servlet对象的配置信息 Servlet Config代表当前servlet在web.xml中的配置信息对象,除了在web.xml中配置servlet的基本映射信息外,还可以配置一个或多个参数,

android 细节之android.view.InflateException: Binary XML file line #95: Error inflating class(out of m)

今天的异常很有意思,叫做android.view.InflateException: Binary XML file line #95: Error inflating class(out of memory) . 其实是因为out of memory,导致 xml是不可能被充气成功,因此activity的onCreate方法中, setContentView(R.layout.***)也就不可能成功调用. 他出现在我有多个教学动画,并且播放的动画,是基于imageView,imageView的

requirejs源码

require.js /** vim: et:ts=4:sw=4:sts=4 * @license RequireJS 2.1.11 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/jrburke/requirejs for details */ //Not using str

eclipse中安装jetty插件并使用

一.eclipse中jetty插件安装: 打开eclipse,依次点击菜单Help->Eclipse Marketplace,在Find后面的框中输入jetty,选择第一项进行install即可. 二.jetty插件的使用: 安装成功后可能要重启eclipse: 然后开始配置: Run>run configurations> 在Jetty Webapp>右键>new,Browser想要启动的项目,配置端口号(我配的是8080),Arguments需要指向一个jetty.xml

Android中Handler导致的内存泄露

http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html Consider the following code: 1 2 3 4 5 6 7 8 9 public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public voi