在前面一篇博客《Android全面解析Handler》一文中,我们认识了Handler的异步通信机制,同时也提到过Handler如果使用不慎将会导致内存泄露。今天主要来讲述一下Handler的内存泄露场景可能存在的场景以及解决方案。

场景一:直接传递外部类引用到静态内部类使用,导致静态内部类间接持有外部类的引用

举个栗子,在一个静态内部类我们想访问外部类的成员属性,怎么办?可不可以直接访问了,答案当然是不能的,如下所示,我们的代码提示是会直接报错的:

public class TestActivity extends AppCompatActivity {private int i = 10;//成员属性iprivate Handler mHandler = new Handler();static class Runnable1 implements Runnable {@Overridepublic void run() {System.out.println("i=" + i);}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);}
}

那是因为就是这个类被编译的时候,static变量会被初始化有且只有一次。但非静态的变量是对象被创建的时候(即new的时候)才存在的,所以static在第一次初始化的时候,这些非静态变量根本不存在,所以也就引用不了了。听到这里,有人会说,那你把外部类的成员属性改成static不就可以访问了。当然,如果把i这个成员变量改成static当然是可以访问到的,但是有没有一种方式是不用改变外部类成员属性,就可以使得静态内部类可以访问的了,当然是有的,且看下面的代码:

public class TestActivity extends AppCompatActivity {private int i = 10;//成员属性iprivate Handler mHandler = new Handler();/*** 如果直接传递外部类引用则依然会造成内存泄露*/static class Runnable2 implements Runnable {//但是不能直接将外部类引用传递进来,因为虽然可以访问外部类的成员属性,但是还是会造成内存泄露TestActivity activity;public Runnable2(TestActivity activity) {this.activity = activity;}@Overridepublic void run() {System.out.println(activity.i);}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);}
}

这种方式就可以实现,但是,同时也带来问题,那就是导致Handler会出现内存泄露。因为,直接传递外部类引用而且还是强引用。大家都知道,在四大引用类型里面,强引用是最容易导致内存泄漏的。这就是Handler的内存泄露的其中一个场景。

出现了内存泄露,就是需要我们必须处理的问题,细心的同学就回发现,既然有四大引用类型,是不是换一种引用方式就可以解决了,那么选用那种引用最合适了,那么就需要我们熟练这四种引用类型的特征,如果你不还不熟悉Java对象的四种引用:《强引用、软引用、弱引用和虚引用》的特性,建议你先查看我的《Java对象的四种引用》一文,这里不在啰嗦。下面直接给出解决方案

解决方案

public class TestActivity extends AppCompatActivity {private int i = 10;//成员属性iprivate Handler mHandler = new Handler();/*** 正确解决方案:使用弱引用,即静态内部类持有外部类的弱引用*/static class Runnable3 implements Runnable {/*** 注意这里应该使用WeakReference,而不是SoftReference,* 虽然使用SoftReference也不会造成OOM,但是当我们退出Activity时是希望Activity尽快被回收的,* 所以使用WeakReference更合适,因为被WeakReference关联的对象在GC执行时会被直接回收,* 而对于SoftReference关联的对象,GC不会直接回收,而是在系统将要内存溢出之前才会触发GC将这些对象进行回收。*/WeakReference<TestActivity> activityWeakReference;public Runnable3(TestActivity activity) {this.activityWeakReference = new WeakReference<TestActivity>(activity);}@Overridepublic void run() {if (activityWeakReference.get() != null) {int i = activityWeakReference.get().i;System.out.println("i=" + i);}}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);}}

我们只需要将强引用改为弱引用即可,使用弱引用,即静态内部类持有外部类的弱引用。至于为何不适用其他引用类型,我在代码里已经给出了想想的注释。

场景二:内部类和匿名内部类持有外部类的引用:

这种内部类和匿名内部类持有外部类的引用导致内存泄漏最常见的莫过于mHandler.postDelayed()

public class TestActivity extends AppCompatActivity {private int i = 10;//成员属性iprivate Handler mHandler = new Handler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);/*** Handler的内存泄露场景*/mHandler.postDelayed(new Runnable() {//这里创建了一个匿名内部类对象@Overridepublic void run() {//匿名内部类对象持有外部类的引用(即持有Activity的引用),所以可以直接访问外部类的的成员属性i//所以会造成内存泄露System.out.println("i=" + i);}}, 1_000);}}

为什么这样使用会造成Handler内存泄漏了?那是因为非静态内部类持有外部类的引用,所以外部类的内存资源一直得不到回收,就可能会造成内存泄漏。知道了原因,下面就给出对应的解决方案。

解决方案一:

onDestroy()方法里调用 mHandler.removeCallbacksAndMessages(null);将消息队列里的Message全部移除:

public class TestActivity extends AppCompatActivity {private int i = 10;//成员属性iprivate Handler mHandler = new Handler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);/*** Handler的内存泄露场景*/mHandler.postDelayed(new Runnable() {//这里创建了一个匿名内部类对象@Overridepublic void run() {//匿名内部类对象持有外部类的引用(即持有Activity的引用),所以可以直接访问外部类的的成员属性i//所以会造成内存泄露System.out.println("i=" + i);}}, 1_000);//解决方案1:在onDestroy()方法里调用 mHandler.removeCallbacksAndMessages(null);将消息队列里的Message全部移除}@Overrideprotected void onDestroy() {super.onDestroy();//解决方案1:在onDestroy()方法里调用 mHandler.removeCallbacksAndMessages(null);将消息队列里的Message全部移除mHandler.removeCallbacksAndMessages(null);}}

解决方案二:

使用静态内部类,静态内部类不会持有外部类的引用,而内部类和匿名内部类会持有外部类的引用:

  //解决方案2:使用静态内部类,静态内部类不会持有外部类的引用,而内部类和匿名内部类会持有外部类的引用mHandler.postDelayed(new Runnable3(this), 10_000);

完整代码如下:

public class TestActivity extends AppCompatActivity {private int i = 10;//成员属性iprivate Handler mHandler = new Handler();/*** 正确解决方案:使用弱引用,即静态内部类持有外部类的弱引用*/static class Runnable3 implements Runnable {/*** 注意这里应该使用WeakReference,而不是SoftReference,* 虽然使用SoftReference也不会造成OOM,但是当我们退出Activity时是希望Activity尽快被回收的,* 所以使用WeakReference更合适,因为被WeakReference关联的对象在GC执行时会被直接回收,* 而对于SoftReference关联的对象,GC不会直接回收,而是在系统将要内存溢出之前才会触发GC将这些对象进行回收。*/WeakReference<TestActivity> activityWeakReference;public Runnable3(TestActivity activity) {this.activityWeakReference = new WeakReference<TestActivity>(activity);}@Overridepublic void run() {if (activityWeakReference.get() != null) {int i = activityWeakReference.get().i;System.out.println("i=" + i);}}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);//解决方案2:使用静态内部类,静态内部类不会持有外部类的引用,而内部类和匿名内部类会持有外部类的引用mHandler.postDelayed(new Runnable3(this), 10_000);}
}

总结

以上俩种场景就是Handler可能造成内存泄露场景的原因,同时我也给出了解决方案。总之一句话,在使用Handler时我们要牢记如下俩条铁的规律,就可以避免使用Handler造成内存泄露。

  1. 使用静态内部类,静态内部类不会持有外部类的引用,而内部类和匿名内部类会持有外部类的引用。所以外部类的内存资源一直得不到回收,就可能会造成内存泄漏。
  2. 属性Java对象的四种引用:强引用、软引用、弱引用和虚引用,明确各种引用的特性和使用特点就可以避免内存泄漏。

Android Handler的内存泄露场景分析相关推荐

  1. Android使用Handler造成内存泄露的分析及解决方法

    Android使用Handler造成内存泄露的分析及解决方法 参考文章: (1)Android使用Handler造成内存泄露的分析及解决方法 (2)https://www.cnblogs.com/xu ...

  2. android handler内存,Android handler之内存泄露原因揭示

    关于handler机制大家可以看前面专题Android面试精选--再聊android Handler机制.今天我们要说的重点是 handler为什么会发生内存泄露? 我们先从源头说起,应用刚启动时,第 ...

  3. android Handler避免内存泄露handler.removeCallbacksAndMessages(null)的使用

    今天,简单讲讲android如何使用 handler.removeCallbacksAndMessages(null). 这个其实很简单,之前我也写了一篇博客将关于handler.removeMess ...

  4. 关于Android 的内存泄露及分析

    博客园 首页 新随笔 联系 管理 订阅 随笔- 137  文章- 6  评论- 145  关于Android 的内存泄露及分析 一. Android的内存机制 Android的程序由Java语言编写, ...

  5. Activity内部Handler引起内存泄露的原因分析

    有时在Activity中使用Handler时会提示一个内存泄漏的警告,代码通常如下: [java] view plaincopyprint? public class MainActivity ext ...

  6. LeakCanary——消除Android中的内存泄露

    2019独角兽企业重金招聘Python工程师标准>>> ##LeakCanary ####简介 LeakCanary是Square公司最近公布的开源项目,旨在消除Android中的内 ...

  7. android native 代码内存泄露 定位方案

    android native 代码内存泄露 定位方案 java代码的内存定位,暂时我们先不关注.此篇文章,主要围绕c c++代码的内存泄露. ** *欢迎留言,交流您所使用的内存泄露定位方案.*c   ...

  8. 7种内存泄露场景和13种解决方案

    什么是内存泄露 什么是内存泄露,通俗的来说就是堆中的一些对象已经不会再被使用了,但垃圾收集器却无法将它们从内存中清除. 内存泄漏很严重的问题,因为它会阻塞内存资源并随着时间的推移降低系统性能.如果不进 ...

  9. Android C++ Native 内存泄露检查工具Raphael使用介绍

    Android C++ Native 内存泄露检查工具使用介绍 实现原理 使用方法 Raphael添加到测试apk 添加项目依赖 同步gradle 启动泄露检测功能 直接使用boardcast功能控制 ...

最新文章

  1. 那些年我用过的SAP IDE
  2. SAPPHIRE NOW阿里云 - 国内传播
  3. 无法直接启动带有“类库输出类型”的项目
  4. BLE 蓝牙网关与蓝牙定位
  5. 蛋白粉有什么作用?搭配这些食物帮助提升免疫力!
  6. OpenCart框架运行流程介绍opencart资料链接
  7. 横向滚动条并且隐藏竖向滚动条
  8. 射手科技公开课第一辑 『项目管理和代码规范』
  9. C++ 从入门到入土(English Version)Section 6: Pointers and Call by Reference
  10. python弹球游戏移动球拍_python pygame实现挡板弹球游戏的代码
  11. Oracle 数据库常用操作总结二之数据库的导入和导出
  12. 调整数组使奇数全部都位于偶数前面
  13. 批量复制文件夹的批处理.bat命令
  14. 晕晕沉沉的一天,ISAPI_Rewrite 2.9破解版竟然是假的
  15. vue动态修改网页标题(也可用于vx里的网页标题)
  16. Iocomp控件官网下载地址破解功能介绍手册
  17. Execel 中文转拼音英文字母
  18. 计算机双硬盘怎么启动第二块硬盘,电脑装两个硬盘怎么设置主从盘_双硬盘设置主盘的方法...
  19. 精美卡通儿童教育班会课件PPT模板
  20. 【解决方案】Ubuntu设置Matlab桌面启动快捷方式

热门文章

  1. sublime text下 Python 问题:TabError: inconsistent use of tabs and spaces in indentation
  2. 最吓人的鬼故事排行榜前十名(转载)
  3. iQOO Neo6入网:骁龙8旗舰平台+80W快充
  4. 黑历史有救了,淘宝可以改账号名了!网友:终于可以“重新做人”了
  5. 合成人声、人脸替换等深度合成信息内容须进行显著标识
  6. 苹果任命软件主管凯文•林奇为“苹果汽车”项目负责人
  7. 被低估的“败家爷们”
  8. 打孔屏+屏下指纹!这届iPhone全是安卓玩剩下的
  9. 叮咚买菜更新招股书:发行价区间为23.5-25.5美元
  10. 华为nova 8系列发布日期曝光:售价还卖贵点?