Handler为什么会导致内存泄漏?其他的内部类没有这个问题?

文章详细解析了Handler的创建方式,强调了它是如何在主线程创建的,以及内部匿名类的作用。讨论了Handler持有Activity引用可能导致的内存泄漏问题,提供了两种解决方法:使用静态Handler和在ActivityonDestroy时移除消息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

handler的创建方式

handler一般使用如下:

public class HandlerActivityJava extends AppCompatActivity {


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.handler_layout);
        new Thread(new Runnable() {
            @Override
            public void run() {
                mHandler.sendEmptyMessage(0);
            }
        });
    }


    Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            testLog();
        }
    };
    
    
    private void testLog(){
        Log.d("liaoliao","主线程收到消息");
    }
}

为什么这个mHandler是在主线程创建的?因为HandlerActivityJava 是运行在主线程的。

Handler是一个内部匿名类

什么是内部匿名类?

匿名内部类是 Java 编程语言中一种特殊的类,它没有显式地定义类名,而是在创建对象时通过传递实现了某个接口或继承了某个类的代码块来定义类。通常,我们使用它来简化代码、减少类的数量和更高效地编写事件处理程序等。

匿名内部类可以使你的代码更加简洁,你可以在定义一个类的同时对其进行实例化。它与局部类很相似,不同的是它没有类名,如果某个局部类你只需要用一次,那么你就可以使用匿名内部类

语法格式: new 类名或者接口名(){  重写方法;};

如上我们可以看出Handler创建出来就是一个内部匿名类。

匿名内部类会持有外部类的引用

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

我们在内部类里面能够直接调用外部类里面的方法,变量等就是因为如此。

比如说上面的代码里面在Handler内部匿名类里面直接调用了外部类的testlog方法。这里贴一段内部类反编译的class代码,可以看到明显持有外部类的引用:


//原代码
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;
     }
}

可参考博文:面试:内部类为什么会持有外部类的引用_java内部类持有外部类引用-优快云博客

可达性分析

我们已经知道了Handler是匿名内部类,匿名内部类持有外部类的对象,handler持有activity。

handler  ----》 activity

我们直到在handler发送消息的时候,最后会走到handler的enqueueMessage方法里面:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

这个里面有一段代码msg.target = this这个this就是指的当前的handler,从这边可以看出handler被message持有。

message ---》handler  ----》 activity

我们知道发送消息时message最后会添加到MessageQueue里面

MessageQueue ---》message ---》handler  ----》 activity

而MessageQueue是在Looper的构造方法里面被创建出来的:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

Looper --》 MessageQueue ---》message ---》handler  ----》 activity

而ActivityThread执行main方法时,执行Looper.prepareMainLooper()方法会为当前线程创建一个Looper,并且把Looper存放到ThreadLocal中:

 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();


public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }


private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

再让我们看看ThreadLocal:

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = this.getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            this.createMap(t, value);
        }

    }

可以看到ThreadLocal里面,每一个线程都对应着一个map,map里面的key是当前的ThreadLocal对象,value是我们存储的Looper对象。

再来看看map是怎么回事:

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            this.table = new Entry[16];
            int i = firstKey.threadLocalHashCode & 15;
            this.table[i] = new Entry(firstKey, firstValue);
            this.size = 1;
            this.setThreshold(16);
        }




static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                this.value = v;
            }
        }

我们可以看到最后的looper是存储在entry里面,entry都是继承弱引用的。

ThreadLocal.ThreadLocalMap->Entry(WeakReference)->Looper->MessageQueue->message->handler->this(activity)

这个GC可达性分析明显不会造成内存泄漏。

我们再来看看主线程的prepareMainLooper方法,里面存在一个sMainLooper变量,这个变量是个啥:

private static Looper sMainLooper; 

 看到这边就清晰明了了,这大哥sMainLooper是static修饰的,是一个GCRoot,整个可达性分析链路是:

Looper(GCRoot)->MessageQueue->message->handler->this(activity)


哪个环节造成内存泄漏

整个调用链是:

Looper(GCRoot)->MessageQueue->message->handler->this(activity)

让我们看到Looper.loop()方法:

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

        ......

        for (;;) {
            Message msg = queue.next(); // might block
               
            ......
 try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } 
             ......

            msg.recycleUnchecked();
        }
    }



 void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

在ActivityThread的main方法里面调用 Looper.loop()执行死循环,死循环首先执行Message msg = queue.next(),只有拿到对应msg后,才会执行handler的dispatchMessage方法,最后处理完消息后执行message的recycleUnchecked方法,target = null,message持有的handler采会被释放。

但是如果handler发送的是一个延时消息,在Message msg = queue.next()方法里面会执行nativePollOnce(ptr, nextPollTimeoutMillis)睡眠,此时activity执行ondestroy方法,正常情况activity应该要被回收掉。但是因为message持有的handler还未被释放(因为recycleUnchecked没有执行),整个gc可达性还是存在的,activty也不会被回收。

解决方法

1.把handler内部匿名类加上static,变成静态对象,不具备调用外部对象的功能。然后使用弱引用

持有当前的activity,WeakReference<MainActivity> mWeakReference。这样一来弱引用包裹的activity在gc的时候就会被回收。

2.在activity ondestroy的时候把消息给remove掉。mHandler.removeCallbacksAndMessages(null),最后也会执行recycleUnchecked方法,message持有的handler被释放。

可参考:

Handler内存泄露的原因是什么? - 简书 (jianshu.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值