一张图总结Handler消息机制

作者: Jdqm | 来源:发表于2018-02-26 10:54 被阅读67次

    文字总感觉很难描述,还不如来一张图总结一下来得清晰。


    Handler

    1.Looper.prepare()

    创建Looper,并保存到sThreadLocal中,创建了一个MessageQueue并赋值给Looper#mQueue

    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));
    }
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    

    2.创建Handler

    从ThreadLocal中获取线程的Looper赋值给Handler的mLooper,Looper的mQueue赋值给Handler的mQueue,所以Handler和其关联的Looper共同持有同一个MessageQueue。

    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(); //Looper赋值给Handler#mLooper
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue; //Looper#mQueue赋值给Handler#mQueue
        mCallback = callback;
        mAsynchronous = async;
    }
    

    3.Looper.loop()开启消息循环

    进入一个for(;;)循环,mQueue.next()取出消息后,交给Handler#dispatchMessage()来处理,当取出的Message为null时退出循环。以下为关键性源码。

    public static void loop() {
        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
            //MessageQueue的next方法取出的消息为null,退出循环
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
    
            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                //取出消息后交给Handler的dispatchMessage来处理
                msg.target.dispatchMessage(msg); 
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            msg.recycleUnchecked();
        }
    }
    

    从注释可以看到,MessageQueue#next()有可能导致线程阻塞,实际上是next()方法中的nativePollOnce()方法会阻塞线程。当处理完成一个Message后调用msg.recycleUnchecked()来回收消息,回收的消息将被缓存到sPool单链表的表头,最大缓存消息数量是50。当调用Handler#obtainMessage()时,就会从这个缓存链表中获取一个Message对象。

    4.对外提供的消息回收接口:Message#recycle()

    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }
    

    5.MessageQueue

    (1)MessageQueue 按Message.when降序排列,在前面的消息先出队(MessageQueue#enqueueMessage、MessageQueue#next)
    (2)barrier的Message与普通Message的差别是target(类型是Handler)为null,只能通过MessageQueue#postSyncBarrier创建 barrier Message
    (3)barrier的Message与普通Message以同样的规则进入队列,但是却只能通过MessageQueue#removeSyncBarrier出栈
    (4)每个barrier使用独立的token(记录在Message#arg1)进行区分
    (5)所有的同步消息(相对与异步消息而言,默认消息都是同步消息)如果barrier之后,都会被延后执行,直到调用MessageQueue#removeSyncBarrier通过其token将该barrier清除,当barrier在队头时,队列中的异步消息照常出队不受影响
    (6)Handler中的对应构造函数被隐藏,但是可以通过调用Message#setAsynchronous指定对应的Message为asynchronous的Message。
    (7)部署barrier(MessageQueue#postSyncBarrier)与清除barrier(MessageQueue#removeSyncBarrier)的相关方法都是隐藏的方法,对外不可见

    6.使用Handler值得注意的事儿

    (1)引起内存泄漏问题

    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 that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    

    异常:The following Handler class should be static or leaks might occur.

    原因:
    由于Handler有可能(绝大部分)会被投递到MessageQueue中的Message#target所引用,在此消息没有被处理的情况下将一直持有,此时非静态内部类的Handler持有外部类实例的引用,例如是一个Activity实例,如果此时Activity退出,将会由于被Handler强引用而无法及时GC,导致内存泄漏。

    通常处理方法:
    ①将Handler改为静态内部类,使用弱引用来应用外部实例,只有若弱引用的对象,在GC时可以回收。关于Java中的四种引用,可以看看这篇文章《Java中的4种引用类型》
    ②在外部类实例销毁时,调用Handler#removeMessage()将消息从消息队列中移除回收掉,这样就能移除Message对Handler的引用,当外部实例销毁时,Handler变成可被GC的对象。

    (2)创建Handler时,提示当前线程没有Looper

    Can't create handler inside thread that has not called Looper.prepare()
    

    原因:
    Looper.prepare()实际上是创建一个Looper传入作为所在线程的局部变量(全局由ThreadLocal与Thread#localValues来保证,简单参考ThreadLocal#get、ThreadLocal#set即可理解),而在真正Looper#loop的时候,是需要所在线程的局部变量的Looper为载体取得所有要处理的消息以及处理的方式的。因此创建Handler的同时是需要保证所在线程已经有了局部变量Looper的实例,才能保证Handler接下来真正运作。

    通常处理方法:
    在创建Handler前,主动调用下Looper.prepare()

    每个线程的的Looper#prepare相对所在线程只能被调用一次,否则会报"Only one Looper may be created per thread"(参见Looper#prepare),之所以主线程直接创建Handler不会抛出类似异常,是因为在程序启动时,系统已经帮我们调用了Looper#prepare(参见ActivityThread#main)

    7.消息池子

    Message中有一个单链表的sPool用来缓存消息,最大缓存数量是50,所以当我们要使用Handler发送消息时,可以通过Handler#obtainMessage()或者Message.obtain()来得到一个消息对象,而不是通过new Message()来得到消息。

    关于ThreadLocal可以看看这篇文章 https://www.jianshu.com/p/517f3d16ad89

    相关文章

      网友评论

        本文标题:一张图总结Handler消息机制

        本文链接:https://www.haomeiwen.com/subject/zylfxftx.html