IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 一切从Android的Handler讲起(八):Handler的内存泄露 -> 正文阅读

[移动开发]一切从Android的Handler讲起(八):Handler的内存泄露

作者:recommend-item-box type_blog clearfix

一切从Android的Handler讲起(八):Handler的内存泄露

??前面肥柴从浅入深,以Handler的基本工作机制为导入,进一步解析Handler机制的内部底层原理、Android触摸事件原理以及Android Framework层对消息机制的应用。这一篇章作为Handler的最后一个篇章,我们依旧从Handler入手,来谈谈内存泄漏的那些事。

一、内存泄露

??内存泄漏是一个老生常谈的问题,也是面试容易问到的问题,那到底什么是内存泄漏呢?

??内存泄漏是指动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,直到程序结束

内存泄漏模型

??java的优势之一就是内置了垃圾回收器GC,它帮助我们实现了自动化内存管理。但是GC再好,也有老马失前蹄的时候,它不能保证提供一个解决内存泄漏的万无一失的解决方案。而内存泄漏通俗的讲就是一部分内存空间明明已经使用了,却没有引用指向这部分空间,造成这片已经使用的空间无法处理的情况。

二、Handler的内存泄露

??当我们尝试写一个Handler的匿名对象的时候,Android Studio会有如下报错,提示我们此写法存在内存泄漏。

Android Studio内存泄漏提示

??那么为何Android Studio会有这种提示?

思考

??答案很简单:因为匿名内部类默认会持有外部类Activity的引用,这样当Activity被销毁时,由于被匿名handler对象所持有而不能被释放,Activity所占用的内存就会泄露

??这是我们都知道的答案,那么问题又来了,为啥匿名内部类会持有外部类引用?

带走

??这就得从匿名内部类的java编译后的.class文件说起,下面是肥柴把上面的匿名内部Handler类编译后的kotlin字节码代码。我们重点看注释1的地方,可以发现编译器为匿名内部类也单独生成了一份.class文件,而且其类名为Outer$1,并为其构造函数添加了一个参数,这个参数就是Outer类的实例,这就是为什么说匿名内部类默认会持有外部类的引用

// ================com/happyfatdoge/learningdemo/MainActivity$handler$1.class =================
// class version 52.0 (52)
// access flags 0x31
public final class com/happyfatdoge/learningdemo/MainActivity$handler$1 extends android/os/Handler {

    /** 注释1 外部Activity引用变量 */
  OUTERCLASS com/happyfatdoge/learningdemo/MainActivity <init> ()V

  // access flags 0x1
  public handleMessage(Landroid/os/Message;)V
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 1
    LDC "msg"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 20 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/happyfatdoge/learningdemo/MainActivity$handler$1; L0 L2 0
    LOCALVARIABLE msg Landroid/os/Message; L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x0
  <init>()V
   L0
    LINENUMBER 17 L0
    ALOAD 0
   L1
    LINENUMBER 17 L1
    INVOKESPECIAL android/os/Handler.<init> ()V
    RETURN
   L2
    LOCALVARIABLE this Lcom/happyfatdoge/learningdemo/MainActivity$handler$1; L0 L2 0
    MAXSTACK = 1
    MAXLOCALS = 1

  @Lkotlin/Metadata;(mv={1, 5, 1}, k=1, d1={"\u0000\u0017\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000*\u0001\u0000\u0008\n\u0018\u00002\u00020\u0001J\u0010\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H\u0016\u00a8\u0006\u0006"}, d2={"com/happyfatdoge/learningdemo/MainActivity$handler$1", "Landroid/os/Handler;", "handleMessage", "", "msg", "Landroid/os/Message;", "LearningDemo.app.main"})
  // access flags 0x19
  public final static INNERCLASS com/happyfatdoge/learningdemo/MainActivity$handler$1 null null
  // compiled from: MainActivity.kt
}

??同理,非静态内部类也是默认持有了外部类引用,而容易造成内存泄漏

嗯嗯

??现在我们回到正题,因为Handler这个匿名内部类持有外部Activity的引用,导致Activity销毁时无法释放其内存,那为何Activity被引用了就无法释放Activity的内存呢?

??这里就涉及到了JVM的垃圾回收机制:如果GC Root到这个对象是引用链可达的话,那么此时就不能被GC垃圾回收掉此对象的内存

??而JVM内能充当GC Root对象的可以分为几种:1). 虚拟机栈/本地方法栈中JNI中的引用的对象。2). System Class Loader/Boot Class Loader加载的类对象。3). 激活状态的Thread 线程。4). 方法区中的常量引用的对象。5). 方法区里的类静态属性所引用的对象,等等。

??肥柴利用内存泄漏工具分析Handler泄漏的真实引用链:Activity -> handler -> message -> queue -> UI线程。用最容易理解的一句话来回答为何会导致无法释放Activity的内存:由于激活状态的Thread线程是GC Root对象,此处对应了UI线程,那么由于UI线程一直处于激活状态,导致了这条引用链上的所有对象都不能被GC内存回收掉

嗯,然后呢?

??那根据这个理论,如果我们现在的handler关联的looper是子线程而非UI线程的话,因为随着子线程运行完毕,子线程的Looper和MessageQueue、Handler对象也随之消亡,这条引用链也就断裂了,Activity销毁后就可以被GC回收掉!

三、Handler内存泄漏的处理

??既然我们知道了内存泄漏的真实原因,如何处理Handler引发的内存泄漏呢?

??最常见的答案便是:将Handler声明为静态内部类,同时为了能够使用到Activity的引用,可以使用弱引用处理Activity引用,避免GC无法释放Activity

??那有更简单更好的方法吗?

傻眼

??其实是有的,这是肥柴也是很多读者最容易忽视的方法,同时也是最简单的方法:就是在Activity onDestroy()时调用handler.removeCallbacksAndMessages(null),这样就把Queue里所有的Message都remove掉了,之前说过Message被Message pool回收掉会reset,因此不会再引用Handler,这条引用链就断掉了

拽

?- - - - - Handler的内存泄露完 - - - - -

结束

??至此关于Handler的相关内容也就结束了,后续如有补充在继续扩展本篇章。

??一、一切从Android的Handler讲起(一):Handler工作机制

??二、一切从Android的Handler讲起(二):Message

??三、一切从Android的Handler讲起(三):Looper的唯一性——ThreadLocal

??四、一切从Android的Handler讲起(四):Looper消息获取

??五、一切从Android的Handler讲起(五):延迟消息实现原理与消息机制的基本原理

??六、一切从Android的Handler讲起(六):Android触摸事件基本原理

??七、一切从Android的Handler讲起(七):Handler在Android系统框架层的应用

??八、一切从Android的Handler讲起(八):Handler的内存泄露

??篇章系列参考资料:https://www.zhihu.com/people/jing-shen-ling-xiu-68-79/posts

撒花

?- - - - - 一切从Android的Handler讲起篇章完 - - - - -

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2022-05-09 12:51:06  更:2022-05-09 12:53:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 1:36:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码