博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入分析 Handler 内存泄露
阅读量:4285 次
发布时间:2019-05-27

本文共 4709 字,大约阅读时间需要 15 分钟。

1. 在 Activity 中直接使用 Handler 时候编译器警告内存泄漏

1.1 Java

public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override public void handleMessage(Message msg) {
// ... } };}

在实际编写中,我们往往会得到如下警告:

In Android, Handler classes should be static or leaks might occur.

1.2 kotlin

//ListAdapter代码中有如下代码:private val handler = object : Handler() {
override fun handleMessage(msg: Message) {
if (msg.what == COUNT_DOWN) {
notifyItemChanged(msg.arg1, COUNT_DOWN_TIME) } }}

在这里插入图片描述

编译后的 java 代码:

private final 
handler;this.handler = new Handler() {
public void handleMessage(@NotNull Message msg) {
Intrinsics.checkNotNullParameter(msg, "msg"); if (msg.what == 1) {
SecondHandCarOrderListAdapter.this.notifyItemChanged(msg.arg1, "countDownTime"); } } };

2. 为什么 handler 会导致内存泄漏呢?

2.1 什么是内存泄漏

一句话: Activity 走完 onDestory 之后应当被销毁但是实际并没有,handler 持有 该 Activity 的引用导致它无法被销毁

Java 虚拟机中使用可达性分析的算法来决定对象是否可以被回收。即通过GCRoot 对象为起始点,向下搜索走过的路径(引用链),如果发现某个对象或者对象组为不可达状态,则将其进行回收。

内存泄漏指的就是有些对象(短周期对象)没有用了,但是却被其他有用的类(长周期对象)所引用,从而导致无用对象占据了内存空间,形成内存泄漏。

所以 Handler 内存泄漏 的问题只说 内部类持有了外部类的引用 是不完整的,没有指出内部类被谁所引用,那么按道理来说是不会发生内存泄漏的,因为内部类和外部类都是无用对象了,是可以被正常回收的。

所以为什么内部类 handler 无法被回收呢?以至于导致它持有的 Activity 也无法正常被回收

2.2 handler 引用路径分析

简单来说,HandlerActivity 发生了内存泄漏,从引用路径来看,是被匿名内部类的实例 mHandler 持有引用了,而 Handler 的引用是被 Message 持有了,Message 引用是被 MessageQueue 持有了…

//引用路径大致酱紫主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity

在这里插入图片描述

  • 当Android应用程序启动时,framework会为该应用程序的主线程创建一个Looper对象。这个 Looper 对象包含一个简单的消息队列 Message Queue, 并且能够循环的处理队列中的消息。这些消息包括大多数应用程序framework事件,例如Activity生命周期方法调用、button点击等,这些消息都会被添加到消息队列中并被逐个处理。主线程的 Looper 对象会伴随该应用程序的整个生命周期

  • 然后,当主线程里,实例化一个 Handler 对象后,它就会自动与主线程Looper 的消息队列关联起来。所有发送到消息队列的消息 Message 都会拥有一个对 Handler 的引用,所以当 Looper来处理消息时,会据此回调 Handler#handleMessage(Message)

  • java 里,非静态内部类匿名类 都会潜在的引用它们所属的外部类。但是,静态内部类却不会

3. 内部类为什么会持有外部类的引用

这是因为内部类虽然和外部类写在同一个文件中,但是编译后还是会生成不同的class文件,其中内部类的构造函数中会传入外部类的实例,然后就可以通过 this$0 访问外部类的成员

eg:

//原代码class InnerClassOutClass{
class InnerUser {
private int age = 20; }}//class代码class InnerClassOutClass$InnerUser {
private int age; InnerClassOutClass$InnerUser(InnerClassOutClass var1) {
this.this$0 = var1; this.age = 20; }}

4. kotlin中的内部类与Java有什么不一样吗

在这处理一下 1.2 中存在的内存泄漏问题

这是因为在kotlin中的匿名内部类分为两种情况:

  • 在Kotlin中,匿名内部类如果没有使用到外部类的对象引用时候,是不会持有外部类的对象引用的,此时的匿名内部类其实就是个静态匿名内部类,也就不会发生内存泄漏。
  • 在Kotlin中,匿名内部类如果使用了对外部类的引用,像我刚才使用了btn2,这时候就会持有外部类的引用了,就会需要考虑内存泄漏的问题。
    所以我特意加了这一句,让匿名内部类持有外部类的引用,复现内存泄漏问题。

同样 kotlin 中对于内部类也是和 Java 有区别的:

  • Kotlin 中所有的内部类都是默认静态的,也就都是静态内部类。
  • 如果需要调用外部的对象方法,就需要用 inner 修饰,改成和Java一样的内部类,并且会持有外部类的引用,需要考虑内存泄漏问题。
    • 用了 inner 关键字就和 Java 内部类一样用法了

5. solution sample code

5.1 四种引用

  • 强引用就是对象被强引用后,无论如何都不会被回收。
  • 弱引用就是在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。
  • 软引用就是在系统将发生内存溢出的时候,回进行回收。
  • 虚引用是对象完全不会对其生存时间构成影响,也无法通过虚引用来获取对象实例,用的比较少

5.2 Java code

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
mActivity; public MyHandler(SampleActivity activity) {
mActivity = new WeakReference
(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(); }}

5.3 kotlin code

private val handler = MyHandler(WeakReference(this))    class MyHandler(var adapter: WeakReference
):Handler(){
override fun handleMessage(msg: Message) {
super.handleMessage(msg) if (msg.what == COUNT_DOWN) {
adapter.get()?.notifyItemChanged(msg.arg1, COUNT_DOWN_TIME) } } }fun destroy() {
handler?.removeCallbacksAndMessages(null) timer?.cancel() timer?.purge() timer = null task = null }

参考链接

转载地址:http://atpgi.baihongyu.com/

你可能感兴趣的文章
C# 本地时间格式,UTC时间格式,GMT时间格式处理
查看>>
Windows系统搭建GitServer--Bonobo Git Server
查看>>
Bootstrap3 datetimepicker控件之smalot的使用
查看>>
小程序Canvas隐藏问题处理
查看>>
基于Zookeeper的Curator分布式锁实现
查看>>
来谈谈 Java 反射机制,动态代理是基于什么原理?
查看>>
JVM 内存模型
查看>>
iOS之苹果自带的json解析NSJSONSerialization(序列化)
查看>>
iOS中坐标转换
查看>>
java 基础二
查看>>
java基础(三)方法/数组/堆栈/
查看>>
java基础(四)二维数组/
查看>>
java基础(五)面向对象/类/对象/形式参数/局部和成员变量
查看>>
java基础(六)关键字/private/this/static/构造方法/
查看>>
java基础(七)/面向对像
查看>>
java基础(八)Math/代码块/继承成员方法指南的关系/继承中成员变量之间的关系/方法的重写/继承中构造方法之间的关系/this和super的区别
查看>>
iOS之AFNetWorking基本用法(一)上传、下载
查看>>
java基础(九)关键字final/多态/抽象类/关键字abstract/接口
查看>>
java中的错误集合
查看>>
java基础(十)形式参数和返回值/链式编程/包/权限修饰符/内部类
查看>>