【Android 源码解析】从源码角度深入分析Handler消息机制

一、引言

说起消息机制,相信每一个Android开发都不陌生。从开始写Android代码的第一天起,我就一直在试图理解handler机制,这几年中也不断去读handler源码。最近有了一些收获,觉得自己已经非常了解handler源码了,故在这里做下总结,希望分享给需要的小伙伴。

首先先看如上这个异常:只有创建View的原始线程才能更新UI。这个异常在什么情况下出现呢?在Android系统中,只能在主线程更新UI,如果在工作线程更新UI,就会报这个异常。出现这个问题的根本原因是:Android中的View不是线程安全的

最简单的办法是给View加同步锁使其变成线程安全的。这样做不是不可以,只是有两点坏处:

1. 加锁会导致效率低下

2. 多个地方更新UI,开发时很容易导致时序错误。

基于以上两点,这种方式被抛弃。所以在Android中,专门设置了一个线程来处理UI控件的更新,这样就做到了“职责明确,各司其职”,我们开发时也很好理解。当其他线程要更新UI,就发送消息交给UI线程去处理。

实现原理呢?一条线程如何发送一个消息给另外一个线程呢?是不是可以找到两个线程共用某个资源的地方?不同进程享用不同的内存空间,但是同一个进程的不同线程享用同一片内存空间,那让其他线程吧要处理的消息放到特定的内存空间上,处理消息的线程来这个内存空间来去消息处理不就可以了吗?事实上,在Android消息机制中,扮演这个特定内存空间的对象就是MessageQueue。Handler和Looper都是为了配合线程切换用的。

其实不仅仅线程之间,不同进程之间进行消息传递也是这个思路,找到一个公用的一个资源点,文件系统啊,共享内存啊。

在Android应用启动时会自动创建一个线程(程序的主线程,负责View的展示,事件消息的分发,所以主线程常常被称为UI线程)。在Android平台中引入Handler,通过在子线程发送消息给主线程来更新UI。

 

二、什么是Handler?

Handler是Android消息机制的上层接口。在我们日常的开发工作中,我们只需要知道如何创建Handler并复写它的handleMessage方法就可以使用Handler了。我们可以简单地把Handler认为是更新UI的一种机制,它可以让耗时操作放在子线程,更新UI的操作放在主线程。

Handler通过发送和处理Message和Runnable对象来关联相应线程的MessageQueue。

 

三、子线程如何更新UI?

  Handler在这个时候就闪亮登场了!!

可以看到,在接口返回数据后,红框框出来的部分是发送了一个Message,而没有直接更新UI。然后在handleMessage中更新UI。

以上就是Handler的一个具体使用。

 

总结一下,Handler该如何使用呢?

(1)post(runnable)

        

        通过查看post(runnable)源码可知,其源码实现其实是通过sendMessage来实现。

(2)sendMessage

        

其背后的原理又是什么呢?

 

四、Handler消息机制图解

首先来看这样一张图,这个图里包含了4大部分:Looper、MessageQueue、Message、Handler。

        

Looper是每个线程独有的,它通过loop方法,读取MessageQueue中的Message,并发送给Handler进行处理。

 

五、从源码角度理解Android消息机制

mHandler.sendMessage(msg)其实最终调用的是sendMessageAtTime(msg,long)方法。

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

它其实就是调用了enqueueMessage方法将Message发送到MessageQueue尾部。接下来就等着别人来调用Handler中的方法了,可是是谁调用的,在哪调用的

Loop.loop()方法一直遍历MessageQueue,阻塞线程,直到获取到一个Message,然后调用Message的一个成员变量target(其实就是Handler)的dispatchMessage(msg

)方法。

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

可以看到最终调用了Handler的handleMessage方法。

 

这里应该注意的是loop方法,它其实是一个死循环,当ActivityThread一启动Looper的loop方法就一直在执行!其中,ActivityThread其实就是我们所谓的主线程。

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
            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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

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

不过在看过下面的源码你可能会疑问:这个loop方法明明在msg为null时return了啊,这不就执行结束了吗?其实不是的。我们来看看queue.next方法,可以看到它其实也是一个死循环,只有当发送过来的消息是“quit”的时候才会返回null。

在我们创建Handler的时候,它需要与Looper关联。MessageQueue就是Looper的成员变量mQueue。如果没有looper,那就会报错“Can't create handler inside thread that has not called Looper.prepare()”。所以在new Handler的时候一定要调用过Looper.prepare(),在主线程中new Handler()没有调用Looper.prepare()是因为主线程默认就调过这个方法。

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


到此,我们就深入理解了消息队列、Handler、Looper三者之间的关系:一一对应!

接着再看Looper类的 loop方法,我们知道,这其实是一个死循环,这里面一直在做一件事:

	    try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }    try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

其中,msg.target就是Handler对象。那么Handler的dispatchMessage做了啥?

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

一目了然,就是在handleMessage。

 

六、Handler引起的内存泄漏以及它的解决办法

    内部类持有外部类的强引用,导致外部Activity无法被释放。

    如何解决呢?

    (1)handler内部持有Activity的弱引用,并把Handler改为静态内部类

    (2)在Activity的onDestroy方法中removeMessageAndCallbacks

 

七、最后

这里从源码角度讲清楚了MessageQueue、Handler、Looper的关系。如有任何疑问,欢迎留言或邮件联系zhshan@ctrip.com,博主每天都会查看。

 

~~~~~~~~~~~~~~~华丽丽的分割线:View.post()到底做了啥?~~~~~~~~~~~~~~~~~~~~

    在日常的开发过程中,很多奇葩问题总可以通过View.post()方法解决。比如,View.post()中的操作执行时,View的宽高已经计算完毕,所以经常看见在Activity的onCreate()里调用View.post()来解决获取View宽高为0的问题。我们来梳理一下,View.post()原理到底是什么,内部都做了啥事。

    View.post()方法很简单,代码很少,我们来分析一下。

    如果AttachInfo不为空,那就调用mAttachInfo.mHandler.post()方法;如果为空,那就调用getRunQueue.post()方法。

   那就找一下,mAttachInfo是什么时候赋值的。通过查看源码,可以看到在dispatchAttachedToWindow中赋值,在dispatchDetachedFromWindow中置空。那dispatchAttachedToWindow什么时候被调用呢?至少我们清楚一点,在Activity的onCreate期间,这个方法还没有调。那为什么View.post()方法可以保证在View宽高计算完毕呢?

    我们来看下getRunQueue()方法里做了什么。

    所以,其实调用的是HandlerActionQueue.post()方法,我们跟进去看一下。

可以看到是将runnable作为参数创建一个HandlerAction对象,然后放到mActions数组里。那保存到数组之后,什么时候会执行呢?我们找到了这个方法executeActions

从名字可以看出这是从数组里取出Action处理,那它是在哪里调用的呢?

可以看到是在dispatchAttachedToWindow生命周期方法里调用的。这下我们的思路就清晰了。当执行View.post()方法时,页面还没有渲染完毕,那就会等到dispatchAttachedToWindow生命周期方法回调时调用。

我们可以猜想,这个方法肯定就是在页面渲染结束后才回调的,这里就不写验证过程了,具体可以去查看源码。

 

参考

https://www.jianshu.com/p/e7b6fa788ae6

https://mp.weixin.qq.com/s/laR3_Xvr0Slb4oR8D1eQyQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值