Handler源码详解及导致内存泄漏的分析

[TOC]

简介

android的消息处理有三个核心类:Looper,Handler和Message,
主要接受子线程发送的数据, 并用此数据配合主线程更新UI。
部分图片来至CodingMyWorld博客,3Q

使用方法

public class LooperThread extends Thread {
    @Override
    public void run() {
        // 将当前线程初始化为Looper线程
        Looper.prepare();

        // ...其他处理,如实例化handler

        // 开始循环处理消息队列
        Looper.loop();
    }
}

通过上面两行核心代码,你的线程就升级为Looper线程了,就具备消息处理的功能!

Looper.prepare()

Handler源码详解及导致内存泄漏的分析_第1张图片
通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,以下源码使用到ThreadLoacal,可以想象成一个线程的属性/变量,想了解更多请点击链接

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {

        //获取当前线程对应线程变量:Looper,重复执行此方法会有如下报错提示
        //
        //"Only one Looper may be created per thread"
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }

        //将当前初始化的Looper对象保存到当前线程变量中
        sThreadLocal.set(new Looper(quitAllowed));
    }

Looper.loop()

Handler源码详解及导致内存泄漏的分析_第2张图片
调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {

        //获得当前线程的Looper对象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

        //获得消息队列
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        //循环消息体
        for (;;) {

            //去出单个消息体
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }


            //向目标对象中分发当前循环到的消息体,下面将会具体讲解
            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            //使用完消息之后需要对它进行回收
            msg.recycleUnchecked();
        }
    }

msg.target.dispatchMessage(msg)解释

Message类中查找可以发现

    /*package*/ Handler target;

其实target就是handler对象,那handler是如何和一个Message发生联系的,稍等?下面移步Handler源码分析

Handler的创建就已经获取了当前线程的Looper和消息队列

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class 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());
            }
        }

        //获取同一线程的Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }

        //获取到同一线程的消息队列
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

真正Message和Handler关联的地方来了

在我们sendMessage()的时候都是先用obtainMessage 来获取一个Message

    public final Message obtainMessage()
    {
        return Message.obtain(this);
    }

移步Message的方法看详细

    public static Message obtain(Handler h) {
        Message m = obtain();

        //是不是so easy,真正的联系在这里
        m.target = h;

        return m;
    }

如果你是使用post(Runnable r) 来发送消息的,那应该构造一个Message来发出去,不信可以看源码
Handler类中:

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    /**
    *构造一个Message
    */
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();

        //与PostMessage不同的是这个runable是给了callback属性
        m.callback = r;
        return m;
    }

具体消息处理:Handler处理消息

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {

            //处理Runable消息
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }

            //处理具体Message,这个是你在消息处理的时候重写的方法实现
            handleMessage(msg);
        }
    }

总结

  1. Lopper–消息的集合和消息的循环
  2. Handler–消息的管理接口和消息的处理
  3. 主线程已经持有Looper,所以不需要Looper.prepare()
    Activity源码查看:
    /*package*/ ActivityThread mMainThread;

ActivityThread源码中查看:

    final Looper mLooper = Looper.myLooper();

导致内存泄漏的分析

内存泄漏场景

  1. 在一个activity中post已个message
  2. 关闭这个activity
  3. 由于某些原因这个message开始执行或者正在执行(如上一个message比较耗时/当前message比较耗时),
    更严重的是你发送一个延时消息前把activity关闭
    参考代码
private Handler mLeakHandler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Logger.d(msg.toString());
        }
    };
    @Override
    protected void onResume() {
        super.onResume();

        //延迟10s来模拟场景
        mLeakHandler.sendEmptyMessageDelayed(0x1,10000);
        finish();
    }
    //省略其他代码

分析及修改方法

由于这个Handler作为内部类声明在Activity内部,普通的内部类对象隐式地保存了一个指向外部类对象的引用,所以这个Handler对象保存了一个指向Activity对象的引用。而这个Handler对象的生命周期可能比Activity生命周期长,比如当有一个后台线程持有该Handler,且该线程在执行一个长时间任务。Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。在Message中存在一个 target,是Handler的一个引用,如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity不会被回收。但是注意这个泄漏时临时的!当这个消息处理完引用关系也就不存在了,下次GC的时候也就能回收啦
修改方法:

private static class MyHandler extends Handler {
        private WeakReference reference;

        public MyHandler(Activity activity) {
            reference = new WeakReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            LeakActivity activity = (LeakActivity) reference.get();
            if (activity != null) {
                Logger.d("activity != null"+activity.toString());
            } else {
                Logger.d("activity = null");
            }
        }
    }

    private final Handler mHandler = new MyHandler(this);

同时你需要在调用一下方法,避免不必要的回调(虽然不会报错了)

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

参考文献:

  1. http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html
  2. http://blog.csdn.net/qjyong/article/details/2158097
  3. http://m.blog.csdn.net/article/details?id=51493352 (代码来源博客)

你可能感兴趣的:(android,android,Handler)