Handler源码分析

Handler机制算是个老生常谈的问题了,最近又看了下源码,决定还是形成文字记录下来靠谱。

1、handler

handler在Android用于不同线程间的通信。使用时,一般会在主线程(接收消息的线程)new一个handler,那就先看下handler的源码:

    public Handler(Callback callback, boolean async) {
        ...
        //获取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;
    }

从该构造方法可以得到以下几个信息:
1、Handler对象依赖于Looper对象。
2、Looper对象中当中持有mQueue对象。
3、Looper中的mQueue对象赋给了Handler成员变量。
3、Looper.prepare()用来创建Looper对象,有了Looper对象后才能创建Handler。
这里引出了Looper这个概念,让我们看下Looper是什么吧。

2、Looper

Looper的创建,即上面提到的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));
    }

Looper的获取:

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static Looper myLooper() {
        return sThreadLocal.get();//从ThreadLocal中获取
    }

Looper的构造方法:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

该构造函数为私有的,也就是说Looper只能由它的静态方法prepare创建。构造方法内创建了MessageQueue对象。

那么Looper又是什么呢,它有啥用?看下源码的注释:

Class used to run a message loop for a thread. Threads by default do
not have a message loop associated with them; to create one, call
{@link #prepare} in the thread that is to run the loop, and then
{@link #loop} to have it process messages until the loop is stopped.

简单的说明下Looper是用来和Thread绑定的,它的内部维护了一个消息队列mQueue,所有发送给Thread的消息都会进入这个队列,然后Looper会循环处理这些消息。
看到这里,我们会有几个疑问:
1、Looper的创建和获取方法中引入了sThreadLocal,这是什么?
2、Looper如何与Thread绑定的?
3、Looper是如何接收消息,如何将消息存入它内部的消息队列mQueue的?
4、Looper是如何处理消息的?
下面就带着这几个疑问,依次解决。

3、ThreadLocal

首先讲下handler的作用,当然相信大家都懂得:假设有两个线程MainThread和SubThread。SubThread中通过获取在MainThread中创建的Handler对象,调用Handler的post或sendMessage方法,将消息传递给MainThread后并在该线程中处理。
由上面对Looper的介绍,我们又知道,实质上,发送的消息,最终交给了与MainThread绑定的Looper对象中的mQueue队列。最终的消息处理也是由这个Looper调度的。
如果让我们自己设计这样一个功能,不难想到,如果一个Looper绑定了多个Thread,那么Looper在接收到消息后处理,该在哪个线程中执行呢?所以每个Looper对象应只绑定一个线程,即每个Looper都是被某个线程独占的。
事实上源码就是这么设计,且每个Thread中只有一个Looper对象。
源码中为了保证每个Thread都有它独立的Looper,采用了ThreadLocal

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

用中文概括下来讲,就是对于同一类型的对象传给ThreadLocal后,ThreadLocal提供了一个绑定策略,它会将该对象绑定至当前的线程中,最终使得每个Thread中该类型的对象都是独立的,互不影响。
如果不太了解的话,可以参考这篇文章:https://www.jianshu.com/p/95291228aff7,看下文章开头的示例大概就明白了。
用在这里,就是ThreadLocal能够让每个Thread中都拥有独立的Looper对象。ThreadLocal是怎样实现这一魔法的,看源码:
Looper内持有静态且final的sThreadLocal成员变量。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

在prepare方法中,创建新的Looper对象,并传给sThreadLocal。

sThreadLocal.set(new Looper(quitAllowed));

ThreadLocal中:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //this,本类对象,如果结合Looper源码,这里的this就是指sThreadLocal。
            map.set(this, value);
        else
            createMap(t, value);
    }

   ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

从源码可以看出,set方法内首先获取当前Thread内的ThreadLocalMap,如果不存在,就先创建。到Thread源码内验证一下,果然有个ThreadLocal.ThreadLocalMap类型的threadLocals 成员变量。从名字xxxMap可以看出,这个成员变量是以键值对形式存储数据的。
结合Looper源码,key即为sThreadLocal,value即为Looper对象。这样,对于每个Thread线程,就都会有它独立的Looper对象了。
到这里,上面提到的四个问题中1和2就解决了。

4、MessageQueue

MessageQueue是在Looper的构造方法内初始化的,Thread和Looper是唯一绑定的,所以每个Thread也只能有唯一的一个MessageQueue!MessageQueue是链表解构的队列,因为消息需要频繁的增删,所以采用链表效率更高。
当我们调用Handler的sendMessage或post发送一个消息时,最终都会调用:

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

这里的queue就是在Looper构造函数内初始化的那个。
消息存入该链表后,Looper的loop方法就会循环遍历这个链表,一次取出消息,最后交给Handler的handleMessage取处理:

    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就是发送消息时的那个handler。
                //最终调用handler的handleMessage方法处理。
                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();
        }
    }

Looper的loop方法,会在创建Looper(Handler)的那个线程中调用,该方法中最关键的是msg.target.dispatchMessage(msg);,target从何而来?看下Handler的enqueueMessage方法:

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

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

是不是熟悉的方法来了!如果msg是runnable事件,调用handleCallback(msg),最终调用runnable的run方法执行;
如果msg是普通事件,就调用 handleMessage(msg)执行,而handleMessage不就是我们通常会重写的handler的方法吗?
同时,因为loop方法在创建Looper的线程中执行,那么loop方法中的所有方法调用,都未涉及到线程切换,所以也都在Looper创建的线程中执行。

5、画个图吧

想了想还是来个图直观点!


handler原理.png

6、总结

Handler这一套机制,说白了也挺简单的。
1、首先需要调用Looper.prepare(),为当前线程创建Looper对象,同时创建的还有MessageQueue,ThreadLocal会用来保证每个线程拥有独立的一套Looper和MessageQueue。
2、然后再new Handler(),它会持有刚才在线程中创建的Looper和MessageQueue对象,从而与线程间接关联。(注意,一个Handler对应一个线程,但一个线程中可以有多个Handler,但多个handler多会持有同一个Looper和MessageQueue对象)。
3、最后在同样的线程中调用Looper.loop()循环遍历MessageQueue链表,该循环是一个阻塞方法,它会一直执行,当消息到来,就会依次取出消息,让后让发送该消息的那个handler处理事件(具体怎么处理取决于我们重写的Handler的handleMessage方法)。由于Looper.loop()方法是在创建Looper的那个线程中调用的,所以handleMessage也会在同一线程中执行。
4、Handler的异步消息处理机制的关键在于:Handler对象对每个线程都是可见的,在不同线程中,可以拿到这个handler发送消息;但Handler对应的Looper和MessageQueue却是每个线程独立存在的;消息最终会在创建Looper的那个线程中被处理,从而达到异步处理的目的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容