Handler 引发的内存泄露分析以及解决方法

Handler 引发的内存泄露分析以及解决方法

Handler是Android系统提供的一种在子线程更新UI的机制,可是使用不当会致使memory leak。严重的话可能致使OOMjava

Java语言的垃圾回收机制采用了可达性分析来判断一个对象是否还有存在的必要性,如无必要就回收该对象引用的内存区域,async

Handler handler ;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

}

而后在其余地方来发送一个延迟消息ide

handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        
    }
}, 500);

咱们通常使用Handler就是这样,可是这样会当Activity销毁后会致使memory leak.oop

缘由就是activity销毁了,可是觉得咱们的Handler对象是一个内部类,由于内部类会持有外部类的一个引用。因此当activity销毁了,可是由于Handler还持有改Activity的引用,致使GC启动后,可达性分析发现该Activity对象还有其余引用。因此没法销毁改Activity,post

可是handler仅仅是Activity的一个内存对象。及时他引用了Activity,他们之间也只是循环引用而已。而循环引用则不影响GC回收内存。ui

其实真正的缘由是Handler调用postDelayed发送一个延迟消息时:this

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

而sendMessageDelayed的实现是线程

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

再往下看code

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

最终是将该消息加入到消息队列中。对象

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

能够看到,在enqueueMessage的实现中。将msg.target = this;

就是讲改Handler对象赋值给了message的target对象。因此message对象就引用了Handler对象''

进而messageQueue对象就引用了Handler对象。这次逐渐明朗。就是messagequeue———message———

Handler——Activity。

因此咱们能够在任一环节作文章便可避免Handler持有Activity对象致使的内存泄露问题。

咱们能够在Activity销毁时将任务队列清空,或者 在Activity 销毁时将Handler对象销毁。

总之,就是在任一环节将该引用链条切换就行了,这样GC就能够销毁Activity对象了。

此时仍是没有触及到问题的核心,就是为何messageQueue为何会持有message对象进而持有Handler对象,致使Activity销毁时还有其余引用。为何Activity销毁时MessageQueue不销毁呢,这才是问题的核心,若是messageQueue销毁了啥问题也没有了。固然咱们也能够在Activity销毁时手动销毁messageQueue对象。这样也能够避免内存泄露。

从这咱们能够看出messagequeue的生命周期比Activity长了。因此才致使这些问题。

其实熟悉Handler机制的话就会明白背后的缘由了

final Looper mLooper;
 final MessageQueue mQueue;

public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

从构造方法咱们能够看出,无参的构造方法最终调用了两参的构造方法。

mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;

这几行代码才是重中之重。

首先调用Looper.myLooper()方法。若是looper为null,说明没有调用looper.prepare()方法。从抛出的运行时异常能够看出来。(ps:因此在子线程使用handler时,第一就是要调用Looper.prepare方法)

looper不为空话话,将looper复制个Handler的looper对象,而后将looper的queue对象赋值给handler的queue对象。

能够说Handler的looper字段和queue字段都是来着looper对象的。

能够看出咱们在Handler里发送的消息最终发送到了handler的queue对象所执行的内存区域,而这片内存区域也是Looper对象的queue对象所指向的。因此说该queue对象里全部的message对象都收到Looper对象的queue对象的管理。

真正的大boss来了,都是Looper搞鬼。

由于咱们是在主线程中初始化的Handler。因此Handler引用的looper对象是在主线程中建立的。

在代码ActivityThread.main()中:

public static void main(String[] args) {
        ....

        //建立Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        //建立ActivityThread对象
        ActivityThread thread = new ActivityThread(); 

        //创建Binder通道 (建立新线程)
        thread.attach(false);

        Looper.loop(); //消息循环运行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Looper.prepareMainLooper();
 
  public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

在prepareMainLooper方法中首先调用了prepare方法,这就是为何咱们在主线程使用Handler时不须要本身手动调动looper的prepare方法的缘由。

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));
}

在prepare方法中首先从sThreadLocal对象中取出looper对象。若是不为null.说明已经初始化过了,直接抛出异常。

没有初始化的话直接初始化而后放到sThreadLocal中。sThreadLocal是一个ThreadLocal类型。持有线程的私有数据。

此时,真相大白了。主线程的ThreadLocal——>looper——>messagequue——>message——>handler——>Acitivity

由于APP在活动中,因此主线程一直存在。looper一直存在,messageQueue一直存在。因此当咱们发送了延迟消息时,而此时Activity销毁的话。天然会引发内存泄露的。

解决方法也很明了了。既然咱们不能再looper层面作文章,就只能在handler和message层面作文章了。在Activity销毁时 将Handler手动置为null,或者将messagequeue 清空,或者将Handler设置为静态内部类。而后内部经过若引用持有Activity对象。总之就是要让Handler和message改放手时就放手

相关文章
相关标签/搜索