[译]Handlers和内部类如何造成Context泄露

本文探讨了Android应用程序中因不当使用Handler和内部类而导致的内存泄漏问题。解释了内存泄漏的原因,并提供了解决方案,包括使用静态内部类和弱引用。

How to Leak a Context: Handlers & Inner Classes
看一下下面代码:

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. 当一个安卓应用第一次启动的时候,安卓框架会给APP的主线程创建一个Looper对象。Looper继承了一个简单的在一个圆环中一个接一个地处理消息对象的消息队列。所有的应用层框架事件(例如Activity的生命周期方法的调用,按钮点击事件等等)都是包含在消息对象的范围内,都会添加到Looper的消息队列中并且一个接一个地被处理掉。主线程的Looper通过应用的生命周期的存在而得以存在。
2. 当一个Handler在主线程被初始化的时候,它和Looper的消息队列关联着。那么被发送到消息队列的消息会对Handler持有一个引用以便当Looper最终会处理这个消息的时候,框架可以调用Handler的handleMessage(Message 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);

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

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

当这个Activity被结束掉的时候,这个延迟的消息会在它被Looper处理掉之前继续在主线程的消息队列里面继续存活十分钟。而这个时候,消息对象持有着这个Activity的Handler一个引用,而这个Handler就对它的外部类(这里就是指SampleActivity)持有着一个隐式引用。这个引用会一直存在直到消息对象被处理掉,从而导致这个Activity的Context不能被GC进行垃圾回收并且泄露所有App的资源。记住,匿名Runnable类那一行也是如此。非静态的匿名内部类会对他们的外部类持有一个隐式引用,从而导致Contexxt会造成泄露。
要想解决这个问题,可以在一个新文件里面继承Handler或者使用一个静态的内部类进行替代。静态的内部类不会对它们的外部类持有一个隐式引用,所以Activity不会造成泄露。如果你需要在内部类里面调用外部类的方法,你可以让这个Handler对Activity持有一个弱引用,那么你就不会意外地泄露一个Context了。为了解决当我们初始化一个匿名Runnable类的时候发生的内存泄露,我们创建了这个类的静态成员变量(因为静态的内部类对象不会对他们的外部类持有引用):

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, 1000 * 60 * 10);

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

静态和非静态的内部类的区别是很微妙的(我还是感觉不出来哪里微妙了),但这是每一个安卓开发者都应该理解的一些东西。那么重点是什么呢?(还以为要翻译成底线哩) 那就是,在一个Activity里面如果内部类对象可以在Activity生命周期外存在,那么就应该避免使用非静态的内部类。相反,更加推荐使用一个静态内部类并且在其内部对Activity保持一个弱引用。

基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究(Matlab代码实现)内容概要:本文围绕“基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究”,介绍了利用Matlab代码实现配电网可靠性的仿真分析方法。重点采用序贯蒙特卡洛模拟法对配电网进行长时间段的状态抽样与统计,通过模拟系统元件的故障与修复过程,评估配电网的关键可靠性指标,如系统停电频率、停电持续时间、负荷点可靠性等。该方法能够有效处理复杂网络结构与设备时序特性,提升评估精度,适用于含分布式电源、电动汽车等新型负荷接入的现代配电网。文中提供了完整的Matlab实现代码与案例分析,便于复现扩展应用。; 适合人群:具备电力系统基础知识Matlab编程能力的高校研究生、科研人员及电力行业技术人员,尤其适合从事配电网规划、运行与可靠性分析相关工作的人员; 使用场景及目标:①掌握序贯蒙特卡洛模拟法在电力系统可靠性评估中的基本原理与实现流程;②学习如何通过Matlab构建配电网仿真模型并进行状态转移模拟;③应用于含新能源接入的复杂配电网可靠性定量评估与优化设计; 阅读建议:建议结合文中提供的Matlab代码逐段调试运行,理解状态抽样、故障判断、修复逻辑及指标统计的具体实现方式,同时可扩展至不同网络结构或加入更多不确定性因素进行深化研究。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值