1 问题背景

博主最近在复习《第一行代码》的第10.2.2章节——在子线程中更新UI,书中给出的在UI主线程中用匿名内部类实现Handler的写法如下:

private Handler handler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case UPDATE_TEXT://在这里可以进行UI操作mTermsView.setText("Nice to meet you");break;default:break;}}
};

如果你不熟悉Java的匿名内部类,可参考这篇文章Java中内部类详解—匿名内部类,否则你将无法理解为什会出现内存泄漏。

但是细心的小伙伴们肯定都注意到了Android Studio对这段代码标黄警告,并给出了提示,其截图如下:

其警告和提示内容如下:

This Handler class should be static or leaks might occur (anonymous android.os.Handler)
Inspection info:Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object. Issue id: HandlerLeak

把这段警告和提示翻译过来如下:

这个Handler类应该是static静态的,否则可能会发生内存泄漏。


检查信息:由于这个Handler被声明为一个内部类,因此它会阻止外部类被垃圾回收。如果Handler对主线程以外的线程使用Looper或MessageQueue,那么就没有问题。


如果处理程序正在使用主线程的Looper或MessageQueue,你需要修正你的Handler声明,修正步骤如下:
1.声明Handler为static静态类;
2.在外部类中,实例化一个WeakReference到外部类,并在实例化处理程序时将该对象传递给处理程序;
3.使用WeakReference对象对外部类的成员进行所有引用。


问题ID:HandlerLeak。

2 问题体现

根据上述的Android Studio的警告和提示,我们按照上述的代码来定义Handler会导致内存泄漏,但是我们都有疑问:什么是内存泄漏,内存泄漏是什么样子,它会导致什么问题?

其中百度百科中对内存泄漏的定义如下:

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

首先我们运行上述会导致内存泄漏的代码的APP,再在Android Studio的Profiler性能监测工具中开启该APP的监测,然后我们在会导致内存泄漏的Handler所在的Activity不停地做横竖屏切换,我们可以观察到如下的APP内存监测结果:

根据上图,我们可明显观察到,在手机不停地横竖屏切换时,APP的内存占用明显地升高。

3 问题分析和理解

在上述问题中,为什么横竖屏切换会导致APP内存占用大小不断地升高呢?博主我先给出原因:

  1. 手机横竖屏切换会销毁并重建当前显示的Activity;
  2. 由于上面的 Handler 匿名内部类定义在UI主线程中,因此使用了UI主线程的 Looper 和 MessageQueue;
  3. MessageQueue 中的 Message 会持有 Handler 对象;
  4. 而Handler匿名内部类对象又持有着外部 Activity 的强引用;

以上四点导致当有 Message 未被处理之前, 外部类 Activity 会一直被强引用,这就导致Activity即使被销毁,也无法被垃圾收集器回收。而这些无法被回收的Activity会一直占用着内存,从而导致APP内存占用大小不断升高。最重要的是,这些占用着内存的Activity已经被销毁了,也就不能提供服务了,却还占用着内存而不释放给其他在提供服务的进程或组件,这不是程序员和用户想看到的。而这就是内存泄漏的具体表现。

如果你不太理解为什么MessageQueue 中的 Message 会持有 Handler 对象,以及为什么Handler匿名内部类对象又持有着外部 Activity 的强引用,你可能需要阅读以下文章:
[1]Android-消息机制
[2]Java:强引用,软引用,弱引用和虚引用

4 问题解决

现在我们已经知道上述代码导致内存泄漏的原因和表现了,那么我们怎么解决这个问题呢?让我们再次回顾以下Android Studio给出的建议和警告:

如果处理程序正在使用主线程的Looper或MessageQueue,你需要修正你的Handler声明,修正步骤如下:
1.声明Handler为static静态类;
2.在外部类中,实例化一个WeakReference到外部类,并在实例化处理程序时将该对象传递给处理程序;
3.使用WeakReference对象对外部类的成员进行所有引用。

按照上述建议,我们可以将代码优化成如下所示:

static class MyHandler extends Handler {//注意下面的LoginActivity类是MyHandler类所在的外部类,即所在的ActivityWeakReference<LoginActivity> loginActivityWeakReference;MyHandler(LoginActivity loginActivity) {loginActivityWeakReference = new WeakReference<>(loginActivity);}@Overridepublic void handleMessage(Message msg) {LoginActivity loginActivity = loginActivityWeakReference.get();switch (msg.what) {case UPDATE_TEXT:loginActivity.mTermsView.setText("Terms was changed");break;default:break;}}
}//实例化一个MyHandler对象
private Handler handler = new MyHandler(this);

在上述代码中,我们采用静态内部类的方式定义了Handler,而静态内部类默认不会持有外部类的引用,接着我们通过WeakReference实现Handler持有外部类Activity的弱引用,这样Handler既能访问外部类Activity的成员,又不影响Activity被垃圾回收期回收。

我们再在Android Studio中观察下这段新编写的代码,发现它再也没有被标黄警告了,因此我们便修复了Handler内存泄漏的问题。

本文参考文献:
[1] [smali] This Handler class should be static or leaks might occur
[2]Android“This Handler class should be static or leaks might occur”警告的处理方法

Handler内存泄漏问题解决方案(Android,第一行代码,This Handler class should be static or leaks might occur)相关推荐

  1. Android第一行代码学习思考笔记(碎片、广播、持久化技术和Android数据库)

    Android第一行代码学习思考笔记(碎片.广播.持久化技术和Android数据库 第四章 手机平板要兼顾--探究碎片 4.1碎片是什么(Fragment) 4.2碎片的使用方式 4.2.1碎片的简单 ...

  2. Android第一行代码-Activity

    文章目录 Android第一行代码 Activity 1.Activity基本用法 2.创建和加载布局 加载布局(在Activity中加载布局) 在AndroidManifest文件中注册(所有的ac ...

  3. Android build.gradle文件详解(转述自《Android第一行代码》第二版)

    Android build.gradle文件详解 1. 最外层目录下的build.gradle文件 1.1 repostories 1.2 dependencies 2. app目录下的build.g ...

  4. Android第一行代码(第一行代码、活动)

    一.第一行代码 1.了解全貌 1.1Android的系统架构 Android的系统架构:Linux内核层.系统运行库层.应用框架层和应用层 1.Linux内核层 Android系统是基于Linux内核 ...

  5. Android第一行代码第二版简要总结

    进入安卓的第一本书(简要概括) 第一章 了解大体Android 1.Android系统架构 Linux内核层:为Android设备的各种硬件提供了底层的驱动. 系统运行库层:通过c/c++库来提供主要 ...

  6. Android开发之LitePal数据库的使用(参考于《Android 第一行代码》)

    配置相关文件 在使用之前要进行相关配置,首先要在app.build.gradle中引入LitePal的依赖(下面最后一行代码) dependencies {implementation fileTre ...

  7. Android第一行代码-Fragment

    文章目录 Fragement(碎片) Fragment的引出 碎片的使用方式(创建类继承Fragment,实现里面onCreateView方法) 动态添加碎片(静态引入使用在元素中的android:n ...

  8. Android第一行代码——第八章多媒体

    使用通知 将程序运行到手机上 MainActivity.java activity_main.xml PendingIntent:延迟的Intent 读取后删除通知图标 通知的进阶技巧 通知的高级功能 ...

  9. 《Android 第一行代码》十一章 Service学习笔记

    Android中Service学习笔记 Service的基本使用方法 Service的启动方式有两种,第一种是使用startService()和stopService()方法来启动和停止Service ...

最新文章

  1. SAP LSMW 物料主数据Basic Data Text数据的导入
  2. mysql 从数据库配置文件_mysql数据库配置文件
  3. VTK:vtkBorderWidget用法实战
  4. JQ 全选后获取选中的值_JQ完全学习版本
  5. kotlin 查找id_Kotlin程序查找圆柱体区域
  6. 基于jQuery实现垂直轮播效果
  7. 基于tkinter模块创建GUI程序(python)
  8. 【报告分享】2021中国人才趋势报告.pdf(附下载链接)
  9. docker安装jdk1.8
  10. win10怎么打开计算机树形,win10系统中显示树形目录文件夹的两种方法
  11. 鲁大师电脑硬件兼容性测试软件,还在用鲁大师?查看电脑硬件信息可以用这些免费的软件!...
  12. 堆密度测定的意义_堆密度的意义是什么 汇美科LABULK 0335
  13. 在两个电子表格中找出相同的姓名
  14. 六类网线和超六类网线的区别
  15. 纯css动画效果--animate的应用
  16. 乐高收割机器人_乐高机器人这个大坑,为啥大家都拽着孩子往里跳?
  17. ts快捷键 vscode_vscode这篇就够了
  18. Paper翻译:《MobileNet Based Apple Leaf Diseases Identification》
  19. java aws_AWS学习笔记(八)--S3 JAVA SDK
  20. MIUI系统ROM固件,小米手机所有历史全部机型合集

热门文章

  1. 车用能源的终极:氢能车落地普及还要多久?
  2. 使用通用的单变量选择特征选择提高Kaggle分数
  3. Harbor仓库添加到k8s集群并提供服务
  4. 阶乘问题——斯特林公式
  5. java 读取dat文件_想知道如何从.dat文件中读取对象
  6. Ajax 01客户端和服务器 | jQuery中的ajax | 服务端接口
  7. Apollo EM planner
  8. ICRA 2024 国际机器人与自动化大会
  9. java.lang.NoSuchMethodException android.graphics.FontFamily报错的解决方案
  10. 以TTX连萌来多层次分析游戏破解