context是如何泄漏的 - Handlers和内部类

标签: context handlers 内部 | 发表时间:2013-01-23 23:19 | 作者:
出处:http://www.iteye.com

本人翻译, 略有改动, 原文地址如下:

http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html

 

考虑如下代码:

 

public class SampleActivity extends Activity { 
    private final Handler mLeakyHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
          /* ... */
        }
    }
}

 

你可能看不出来这段代码会造成内存泄漏, 确实, 它不那么容易被发现. 如果你运行Android的lint工具, 它会给你一个警告, 提示你把handler定义成静态的(static), 否则可能造成内存泄漏. 但内存泄漏是怎么发生的呢?

 

首先, 我们应该知道如下几点:

1: 当一个android程序启动时, 框架层(framework)会为程序的主线程创建一个Looper对象. 该对象实现了一个简单的消息队列, 循环不断的处理队列上的消息对象(Message), 主线程上的Looper对象在程序的整个生命周期中一直存在.

2: 当一个Handler在主线程上被实例化时, 它就与Looper的消息队列关联到一起了. 队列上的Message对象持有一个handler的引用, 这使得Looper处理到某一个Message时, 能够调用handler.handleMessage()方法.

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);
 
    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      public void run() { }
    }, 600000);
     
    // Go back to the previous Activity.
    finish();
  }
}
 

 

当Activityfinish后, 我们发出的那个"延迟处理消息"将在主线程的消息队列中保持10分钟, 直到该消息最终被处理. 由于消息持有一个handler的引用, 而handler又持有一个它的外部类-SampleActivity的引用, 这样就阻止了activity的context被垃圾回收, 从而泄漏了Activty引用的所有的应用资源. 注意上述例子中的匿名的Runnable对象也一样造成了context的泄露.

 

要避免这个问题, 就需要将Handler改为静态内部类. 如果你需要在Handler中调用Activity外部类的方法, 你可以在handler中使用一个WeakReference来持有activity对象.

(注意我们将Handler和Runnable都定义成了static的)

public class SampleActivity extends Activity {
  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 final static Runnable sRunnable = new Runnable() {
      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, 600000);
     
    // Go back to the previous Activity.
    finish();
  }
}

 

 

结论: 在Activity中使用非静态的内部类时, 尽量避免内部类生命周期超出了Activity之外. 类似的例子还有AsyncTask.

=================================

补充例子:

如果你看过PendingIntent的源代码, 你会看到它有一些send(Handler...)的方法, 如果某个Activity调用了PendingIntent.send(...), 并且传入一个非静态的内部Handler类, 当activity被销毁后, 内部类仍然持有它的引用, 导致它无法被垃圾收集.

当然, 如果你没有像这样不当的发布一个Handler到其它的类, 你就不用担心泄露发生.

=================================​

如果你不想每次都创建一个WeakReference, 可以先创建这样一个通用类:

 

public abstract class WeakReferenceHandler<T> extends Handler {
    private WeakReference<T> mReference;
 
    public WeakReferenceHandler(T reference) {
        mReference = new WeakReference<T>(reference);
    }
 
    @Override
    public void handleMessage(Message msg) {
        if (mReference.get() == null)
            return;
        handleMessage(mReference.get(), msg);
    }
 
    protected abstract void handleMessage(T reference, Message msg);
}
 

 

 

 

 

--------------------END------------------------



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [context handlers 内部] 推荐:

context是如何泄漏的 - Handlers和内部类

- - ITeye博客
本人翻译, 略有改动, 原文地址如下:. 你可能看不出来这段代码会造成内存泄漏, 确实, 它不那么容易被发现. 如果你运行Android的lint工具, 它会给你一个警告, 提示你把handler定义成静态的(static), 否则可能造成内存泄漏. 首先, 我们应该知道如下几点:. 1: 当一个android程序启动时, 框架层(framework)会为程序的主线程创建一个Looper对象.

白话Block Formatting Context

- MArCoRQ - UED TEAM,用户体验设计,web前端开发
一,啥是Block Formatting Context. 当涉及到可视化布局的时候,Block Formatting Context提供了一个环境,HTML元素在这个环境中按照一定规则进行布局. 一个环境中的元素不会影响到其它环境中的布局. 为了让我们有个感性的认识,举个不太合适的例子. 你可以把一个页面想象成大的集装箱,这个集装箱里装的货物就是HTML元素.

LBS 已兴,Context Awareness 未起

- - 爱范儿 · Beats of Bits
搜索一下 App Store 我们能发现,很多应用也承诺了以上的功能,可是当下载下来一看,就总会有这样那样的失望. 我们的智能手机虽然被冠了智能的名字,可还无法有效的感知我们人造的大千世界. 与此同时,被期以厚望的各个纯 LBS 应用,也正在遭遇憧憬破灭的尴尬. 徽章图像越做越精美,能吸引到去点击的用户也寥寥;网上的签到看去有点热闹,可也不能打动店家的腰包;对了,我们还可以摇一摇.

从Java视角理解CPU上下文切换(Context Switch)

- - 淘宝网通用产品团队博客
从Java视角理解系统结构连载, 关注我的微博( 链接)了解最新动态在高性能编程时,经常接触到多线程. 起初我们的理解是, 多个线程并行地执行总比单个线程要快, 就像多个人一起干活总比一个人干要快. 然而实际情况是, 多线程之间需要竞争IO设备, 或者竞争锁资源,导致往往执行速度还不如单个线程. 在这里有一个经常提及的概念就是: 上下文切换(Context Switch).

Context扩展:神奇切换扩展组合

- guoan - Chrome迷
打网游的同学一定熟悉这个:物品快捷操作栏位置有限,但是东西又太多,所以一定会出现类似这样的分组:. 按一下按钮,就可以快速切换,让有限的空间承载数倍的功能性. 对于 Chrome 的某些死变态用户来说,扩展栏里有十多二十个扩展一点都不稀奇(我朋友就是. 那这类用户有没有类似网游快捷物品栏的需求呢. Context 就是这么一个扩展,让你设置好扩展的组合,然后快速一键切换:.

[原]内部OA

- - 阿朱=行业趋势+开发管理+架构
我发现OA正在不断瓦解,一块块模块正在拆分独立,越做越专. 当然,现在仍然有许多小公司需要一个什么都有但都很薄的在线SaaS租用. 而且各块都在APP化、微信公众账号接入. 我把这些列出来的目的是:供大家寻找思路,单独做专. 3、分享学习会、文档、现场照片、评论交流. 1、活动发布、分享转发、扫码关注.

LevelDB内部实现

- Ben - NoSQLFan
本文是一篇转载的翻译文章,翻译对象是LevelDB的官方文档中实现一章,主要描述了LevelDB内部的数据结构,文件结构及相关的存储,压缩恢复等功能的实现过程,看完后你就能知道,LevelDB为什么会叫这个名字了. 作者:Jeff Dean, Sanjay Ghemawat. 原文:leveldb.googlecode.com.

Facebook的内部晋升

- Xiaoshan - 《程序员》杂志官网
Facebook前工程总监黄易山(Yishan Wong)撰写了一系列文章,很好地总结了Facebook卓越研发文化中的宝贵经验. 本刊将继续连载这一系列,本文是第三篇. 建设一家健康长久的公司,“从公司内部提拔管理者”是一条广为人知的建议. 这条建议也同样适用于规模较小、发展迅速的创业公司. 对于超速发展的创业公司来说,秉持内部晋升的方针既非常有必要,同时又非常困难,具体有以下几个原因.

MongoDB索引内部实现

- Rehtron - NoSQLFan
在数据库优化中,索引优化是非常重要也是每一个数据库使用者必须了解的. MongoDB的索引采用BTree结构,除了其地理位置索引外,数据索引本质上和MySQL 的 BTree索引没有什么差别. 下面是一个接近200页的PPT,对MongoDB读写操作相关的索引操作进行了图文讲解. 如果你对数据库索引的实现原理感兴趣,可以仔细看看.