Android开发中Activity下Handler引起的内存泄漏问题

1. 可能引起内存泄漏的场景


public class MainActivity extends FragmentActivity {

      @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    /*
    * 创建一个handler对象,用来处理消息
    */
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
}

2. 引起内存泄漏的原因

mHandler对象的创建,相当于在Activity的内部创建了一个匿名内部类。在一个外部类中包含一个普通内部类(非static修饰),包括匿名内部类、局部内部类、普通内部类等情况。当创建一个内部类的对象时,这个内部类的对象会隐式的持有这个外部类对象的引用。即,创建mHandler对象时,这个对象的内部会隐式的持有MainActivity的引用,这就会导致,如果mHandler不销毁,则导致Activity无法被回收,就造成了内存泄漏。

3. 为什么Handler不会被销毁

3.1 先来看看Handler的创建

public class Handler {
    /*
    *  轮询器,在一个线程中循环遍历MessageQueue,当有消息需要处理时,交给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());
            }
        }
        //3.1.1
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //3.1.2
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

 }

3.1.1 mLooper = Looper.myLooper();

  1. Looper是一个轮询器;
  2. Looper对象中有一个MessageQueue的引用,用来记录当前Looper需要轮询的消息(MessageQueue中有一个Message对象,Message对象是一个链表式的结构,需要处理的消息按照需要处理的时间进行排序);
  3. 有一个静态的ThreadLocal(本质是一个map),用来存储线程以及对应的looper(每个线程只能有一个looper,通过调用Looper.prepare()方法创建,如果一个线程创建多个looper,会抛出异常);
  4. 这里得到的mLooper就是到ThreadLocal中去取,当然,mHandler中取出的是主线程对应的Looper。

3.1.2 mQueue = mLooper.mQueue;

  1. 记录当前线程对应的MessageQueue。

3.2 Handler发送消息的机制

到这里,Handler对象的创建就完成了,没有发现有什么特殊的地方,也没有导致内存泄漏的理由啊。接着往下看,我们之所以创建Handler的对象,无非就是发消息,以及处理消息。

3.2.1 Handler发送消息

public class Handler {
    //发送消息
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

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

    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(MessageQueue queue, Message msg, long uptimeMillis);方法体中,msg.target = this; 即msg对象持有了当前Handler的引用。这就意味着,在这个msg处理完之前,将一直持有当前handler对象的引用,而handler对象又持有一个隐式的Activity对象的引用,就导致在msg未销毁之前,Activity无法回收,从而引起内存泄漏。

4. 解决方案

之所以内存泄漏,无非就是因为内部类隐式的持有外部类对象的引用,导致外部类对象无法被回收。这样,我们只需要把非静态的内部类改成静态的,这样,静态的内部类就不会持有外部类对象的引用了,也就不会引起内存泄漏的问题了。还是看代码。

/*
* 处理消息的接口,便于在不同的Activity中实现不同的HandleMessage方法
*/
public interface IHandleMessage {
    void handleMessage(Message msg);
}

public class MainActivity extends FragmentActivity implements IHandleMessage {

    private MyHandler mHandler = new MyHandler(this);


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendEmptyMessage(0);
    }

    @Override
    public void handleMessage(Message msg) {

    }

    static class MyHandler extends Handler {

        private WeakReference<IHandleMessage> mWeakReference;

        public MyHandler(IHandleMessage handleMessage) {
            mWeakReference = new WeakReference<>(handleMessage);
        }


        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mWeakReference != null) {
                mWeakReference.get().handleMessage(msg);
            }
        }

    }
}

5. 总结

本篇内容仅为个人经验的一些总结,仅供参考,如有错误,望大牛予以指正。

5.1 关于内部类

普通的内部类(非static修饰),创建的内部类对象都会隐式的持有外部类对象的引用。

5.2 关于Looper

一个线程只能有一个Looper与之对应,且每个Looper对应一个MessageQueue。

5.3 关于Handler

Handler可以在子线程中创建 ,只是在创建时,需要调用Looper.prepare()方法先创建Looper。

5.4 关于Message

Message发送消息以后,会将发送消息的Handler引用传递给Msg对象,在处理时,通过当前Msg拿到target对应的Handler对象进行消息的处理,至于处理的线程,当然就是创建Handler的线程。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页