Android Handler内存泄漏详解及其解决方案

 更新时间:2018年08月12日 08:52:35   作者:zejian_  
在android开发过程中,我们可能会遇到过令人奔溃的OOM异常,这篇文章主要介绍了Android Handler内存泄漏详解及其解决方案,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

关联篇:深入Android的消息机制源码详解-Handler,MessageQueue与Looper关系

关联篇:HandlerThread 使用及其源码完全解析

在android开发过程中,我们可能会遇到过令人奔溃的OOM异常,面对这样的异常我们是既熟悉又深恶痛绝的,因为造成OOM的原因有很多种情况,如加载图片过大,某已不再使用的类未被GC及时回收等等......本篇我们就来分析其中一种造成OOM的场景,它就是罪恶的内存泄漏。对于这样的称呼,我们并不陌生,甚至屡次与之"并肩作战",只不过它就是一个猪队友,只会不断送塔.......

本篇分为3部分:

1.Handler内存泄漏例子说明以及原理阐明

2.问题验证(如果感觉繁琐请直接跳过)

3.Handler内存泄漏解决方法

1.Handler内存泄漏例子说明以及原理阐明

Handler,我们已经相当熟悉了,而且经常用得不亦乐乎,但就是因为太熟悉了,才会偶尔被它反捅一刀,血流不止......还记得我们曾经满怀信心地使用着如下的优美而又简洁的代码不?


不怕你吓着,实话告诉你,这个代码已经造成内存泄漏了!!!不相信?我们使用Android lint工具检测一下该类的代码:


面对现实吧,那为什么会这样呢?在java中非静态内部类和匿名内部类都会隐式持有当前类的外部引用,由于Handler是非静态内部类所以其持有当前Activity的隐式引用,如果Handler没有被释放,其所持有的外部引用也就是Activity也不可能被释放,当一个对象一句不需要再使用了,本来该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏(上面的例子就是这个原因)。最终也就造成了OOM.......我们再来段清晰的代码,我们来使用mHandler发送一个延迟消息:


分析:当我们执行了HandlerActivity的界面时,被延迟的消息会在被处理之前存在于主线程消息队列中5分钟,而这个消息中又包含了Handler的引用,而我们创建的Handler又是一个匿名内部类的实例,其持有外部HandlerActivity的引用,这将导致了HandlerActivity无法回收,进行导致HandlerActivity持有的很多资源都无法回收,从而就造成了传说中的内存泄露问题!

2.问题验证(如果感觉繁琐请直接跳过)

为了进一步验证内存泄漏问题,我们在该类中创建一个int数组,该数组分配的内存大小为2m,然后我们用DDMS来查看heap内存,然后使用GC回收,看看内存会不会有变化:

第一次启动app时,head内存为12.5M,已经分配内容(Allocated):8.5M,空闲内存:4M,我们频繁点击GC按钮,内存并没有发生明显变化,现在我们点击手机返回健,推出应用,然后再重新进入,同样检测一下head内存:


我们发现head内存:20.5M,Allocated:16.5M,Free:4M,堆内存和已经分配内存近乎翻倍,我们继续频繁点击GC, 看看能否被回收?结果内存并没有明显变化,现在我们继续点击手机返回健,推出应用,然后再重新进入,同样再次检测一下head内存:


我们发现head内存:28.5M,Allocated:24.5M,Free:4M,堆内存和已经分配内存又增加了,而且无论我们如何点击GC回收内存,内存都没有明显变化,而且每启动一次该页面,内存就增加一倍!这也就说存在在某个类只创建而没销毁的情况,其实就是存在内存泄漏问题。我们使用MAT工具进一步验证这个问题,我们来看一组Histogram的数据和dominator tree数据,首先是Histogram的数据:


dominator tree数据:


同时存在三个一样的HandlerActivity和内部类,这就足以说明HandlerActvity只有创建没被销毁了吧,也就是说Handler造成的内存泄漏真的存在。

3.Handler内存泄漏解决方法

解决这个问题思路就是使用静态内部类并继承Handler时(或者也可以单独存放成一个类文件)。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理。另外关于同样也需要将Runnable设置为静态的成员属性。修改后不会导致内存泄露的代码如下:

package com.zejian.handlerlooper;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import java.lang.ref.WeakReference;
/**
 * Created by zejian on 16/3/6.
 */
public class HandlerActivity extends Activity {
 //创建一个2M大小的int数组
 int[] datas=new int[1024*1024*2];
// Handler mHandler = new Handler(){
//  @Override
//  public void handleMessage(Message msg) {
//   super.handleMessage(msg);
//  }
// };
 /**
  * 创建静态内部类
  */
 private static class MyHandler extends Handler{
  //持有弱引用HandlerActivity,GC回收时会被回收掉.
  private final WeakReference<HandlerActivity> mActivty;
  public MyHandler(HandlerActivity activity){
   mActivty =new WeakReference<HandlerActivity>(activity);
  }
  @Override
  public void handleMessage(Message msg) {
   HandlerActivity activity=mActivty.get();
   super.handleMessage(msg);
   if(activity!=null){
    //执行业务逻辑
   }
  }
 }
 private static final Runnable myRunnable = new Runnable() {
  @Override
  public void run() {
   //执行我们的业务逻辑
  }
 };
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_handler_leak);
  MyHandler myHandler=new MyHandler(this);
  //解决了内存泄漏,延迟5分钟后发送
  myHandler.postDelayed(myRunnable, 1000 * 60 * 5);
 }
}

Handler的内存泄漏问题到此分析解决完成。其实产生内存泄漏的还有好几种情况,比如多线程造成的内存泄漏,静态变量造成的内存泄漏,单例模式造成的内存泄漏等等.......当然这些不在本篇的范围内,就不过多分析啦。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Android Webview的postUrl与loadUrl加载页面实例

    Android Webview的postUrl与loadUrl加载页面实例

    这篇文章主要介绍了Android Webview的postUrl与loadUrl加载页面实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-03-03
  • Android实现从本地图库/相机拍照后裁剪图片并设置头像

    Android实现从本地图库/相机拍照后裁剪图片并设置头像

    玩qq或者是微信的盆友都知道,这些聊天工具里都要设置头像,一般情况下大家的解决办法是从本地图库选择图片或是从相机拍照,然后根据自己的喜爱截取图片,接下来通过本文给大家介绍Android实现从本地图库/相机拍照后裁剪图片并设置头像,需要的朋友参考下
    2016-02-02
  • 解析android 流量监测的实现原理

    解析android 流量监测的实现原理

    本篇文章是对android中流量监测的实现原理进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • Android应用中利用ViewPager实现多页面滑动切换效果示例

    Android应用中利用ViewPager实现多页面滑动切换效果示例

    这篇文章主要介绍了Android应用中利用ViewPager实现多页面滑动切换效果示例,配合了Fragment和PagerTabStrip,需要的朋友可以参考下
    2016-03-03
  • Rxjava实现发送验证码倒计时功能

    Rxjava实现发送验证码倒计时功能

    这篇文章主要为大家详细介绍了Rxjava实现发送验证码倒计时功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-06-06
  • Android通过XListView实现上拉加载下拉刷新功能

    Android通过XListView实现上拉加载下拉刷新功能

    这篇文章主要为大家详细介绍了Android通过XListView实现上拉加载下拉刷新功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • Android自定义表格控件满足人们对视觉的需求

    Android自定义表格控件满足人们对视觉的需求

    随着人们对视觉的需求,基本组件已无法满足人们求新求异的要求,于是我们常常会自定义组件,用来实现更美观的UI界面,接下来将介绍Android如何自定义表格控件,感兴趣的朋友可以了解下,或许对你学习自定义控件有所帮助
    2013-02-02
  • Android封装常用工具类的示例详解

    Android封装常用工具类的示例详解

    这篇文章主要为大家整理了一些Android封装的常用工具类,包括日志封装类、线程封装类、解压缩类等,文中的示例代码讲解详细,有需要的可以参考下
    2024-03-03
  • android多行标签热点示例

    android多行标签热点示例

    这篇文章主要介绍了android多行标签热点示例,需要的朋友可以参考下
    2014-04-04
  • Flutter软键盘的原理浅析

    Flutter软键盘的原理浅析

    大家应该都知道目前Flutter官方是没有自定义键盘的解决方案,下面这篇文章主要给大家介绍了关于Flutter软键盘原理的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-10-10

最新评论