手把手带你从源码的角度全面理解Handler、Looper、MessageQueue之间的关系

前言:

以前都是看别人的博客、书来分析Handler的源码,没有自己真正系统的研究源码,导致虽然看了那么多博客,还是似懂非懂,理解的不够透彻,所以说看别人的博客只是帮助你理解,还是得自己去看源码深入研究下,才能真正理解透彻、全面。下面是我自己看源码后的一些心得体会,希望能对大家研究源码起到一定的辅助作用.

目标:

首先要明确几个目标,我们要解决以下几个问题:
a.线程是如何与Looper关联起来的?
b.一个线程里面可以有几个Looper?
c.消息是怎么从一个线程传递到另一个线程的?
d.Handler、Looper、MessageQueue三者之间的关系是怎样的?

分析源码前,先举个栗子:

MessageQueue相当于一个池塘,Message就是池塘里面的水,Looper就是一台抽水机,现在我们要用这台抽水机把池塘里的水抽出来,但是首先我们这个池塘开始是没有水的,Handler就相当于一个人,人首先会往池塘里放水(调用handler.sendMessage()等方法),然后还要记得的是,要给我们的抽水机Looper插上电啊,没电你怎么抽水?

插电就是调用Looper.loop()方法,水从池塘抽出来后,又会交给人Handler去处理(通过handler的handleMessage)。
下面我们就开始分析源码吧:
通常我们使用Handler来做Android线程间通信都会在new一个Handler对象,那么我们便从Handler的构造方法看起
1、

    public Handler() {
        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 = null;
    }

我们可以看到上面的
mLooper = Looper.myLooper();方法
那么我们进入这个方法一探究竟
2、

 public static final Looper myLooper() {
        return (Looper)sThreadLocal.get();
 }

它只是返回sThreadLocal的get()方法后强转成了Looper对象。
那么sThreadLocal又是个是啥呢?咱先不去管它。咱先去看看它的get()方法。代码如下:
3、

    @SuppressWarnings("unchecked")
    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

可以看到它首先调用了Thread currentThread = Thread.currentThread();获取到了当前线程,这个大家都很熟悉,就是说我们在哪个线程里面new Handler();拿到的自然也就是哪个线程。然后下面一行代码又调用Values values = values(currentThread);方法,传入当前线程对象获得了一个Values对象,那么这个Values又是个啥呢?它是ThreadLocal的一个静态内部类,Values其实就是Map,它的实现方式基本和HashMap一样(这个大家可以自己去仔细阅读源码,我这里就不贴出来了,代码有些长),那么我们继续看这个values(Thread)方法.

Values values(Thread current) {
        return current.localValues;
}

这个方法是ThreadLocal的一个方法,它的代码很简单,只有一行,返回了传入线程对象的localValues属性,而这个Thread的localValues属性在Thread类里面是这样声明的:

ThreadLocal.Values localValues;

可以看出它其实就是一个Values对象,其实就是一个Map集合,而且在Thread类里面并没有对它进行初始化。那么我们回到上面标号为3的代码处的Values values = values(currentThread)这句代码,现在我们便知道了原来这句代码是返回了当前线程currentThread的一个属性localValues,而这个属性就是一个Map集合。
然后继续往下看get方法里的这段代码

  if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
  } else {
            values = initializeValues(currentThread);
  }

之前我们也说了Thread类里面localValues并没有初始化,也就是我们拿到的values对象是为null的,那么理所当然它走的是else代码块,可以看到里面对values进行了赋值。很简单一行代码,给values赋值。

 Values initializeValues(Thread current) {
        return current.localValues = new Values();
 }

最后get()方法的返回值(T) values.getAfterMiss(this);之前说了,values其实是一个HashMap,它的getAfterMiss其实就是和HashMap的get方法一样,只是换了个名字罢了。阅读源码其实可以发现Values它是以ThreadLocal对象为键,Looper对象为值的,所以ThreadLocal和Looper是一一对应的,而ThreadLocal又是和当前线程一一对应,所有Looper也和当前线程一一对应了。因为是ThreadLocal里面调用这个方法,所以在values.getAfterMiss(this)方法传的这个this就是ThreadLocal对象,取出与一一对应的Looper返回。
最后回到标号为 2的代码, return (Looper)sThreadLocal.get();这句就是返回了与sThreadLocal一一对应的Looper对象。

然后我们再看到标号为1的那段代码,在 mLooper = Looper.myLooper();调用后会有下面这个判断:

        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
    ```
   就是说当前mLooper为空就会报异常,必须先创建一个Looper对象才行。其实在主线程中已经默认为我们创建了这个Looper对象(这个可以在ActivityThread的main方法中看到)。但是在如果你想要在子线程中new一个Handler去处理消息前,就必须去调用Looper.prepare() 去创建一个Looper。代码如下:

 public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
 }

可以看到prepare方法里面首先就做了一个判断sThreadLocal.get()是否为空,这个方法我们已经分析过了,它返回的是当前线程(new Handler的那个线程)对应的Looper对象,如果不为空,就会抛异常,也就证明一个线程里面只能有一个Looper,创建两次或多次就会出错,如果为空,会看到它又调用了ThreadLocal的set方法,并把new出来的Looper作为参数传了进去。set方法具体代码如下:

public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
}

之前我们分析过ThreadLocal的get方法,那么它的set方法也是差不多的,最后那句代码以当前ThreadLocal对象为键,value(也就是我们的Looper对象)为值存入到values这个map中。

下面我们再来分析MessageQueue:
首先看一段在线程中new Hnadler的常见代码:

 new Thread(new Runnable() {  
            public void run() {  
                Looper.prepare();  
                Handler handler = new Handler(){  
                    @Override  
                    public void handleMessage(Message msg) {  
                           ......
                    }  
                };  
                handler.sendEmptyMessage(1);  
                Looper.loop();  
            };  
 }).start();  

Looper.prepare之前我们已经说到了,主要是下面的Looper.loop()方法,我们进入这个方法瞧瞧:

  public static final void loop() {
        Looper me = myLooper();
        MessageQueue queue = me.mQueue;//第3行代码
        while (true) {
            Message msg = queue.next(); // might block
            //if (!me.mRun) {
            //    break;
            //}
            if (msg != null) {
                if (msg.target == null) {
                    // No target is a magic identifier for the quit message.
                    return;
                }
                if (me.mLogging!= null) me.mLogging.println(
                        ">>>>> Dispatching to " + msg.target + " "
                        + msg.callback + ": " + msg.what
                        );
                msg.target.dispatchMessage(msg);//第18行代码
                if (me.mLogging!= null) me.mLogging.println(
                        "<<<<< Finished to    " + msg.target + " "
                        + msg.callback);
                msg.recycle();
            }
        }
  }

第3行代码获得了1个当前Looper的MessageQueue对象,那么我们再去看看me.mQueue是在什么时候初始化的,代码如下:

 private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
 }

从上面这段代码看出,它是在Looper的构造方法里面就给创建了。还记得不,Looper.prepare()方法最后那行代码sThreadLocal.set(new Looper()),也就是说在Looper.prepare()调用的时候我们的 MessageQueue就已经创建了。
然后我们继续看loop方法,里面用while(true)开启了一个死循环,不断的从MessageQueue取Message进行处理。

再看到loop方法的第18行代码 调用了msg.target.dispatchMessage(msg),那么msg.target的又是个什么鬼?从意思上知道它是"目标",进入到Message里面看可以知道它就是一个Handler。我们再调用handler.sendEmptyMessage(1); 发消息的时候其实最终会调用Handler的sendMessageAtTime(Message msg, long uptimeMillis)方法(可自行看源码,为了简便,中间省了一些过程),代码如下:

 public boolean sendMessageAtTime(Message msg, long uptimeMillis){
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
            msg.target = this;//第5行代码
            sent = queue.enqueueMessage(msg, uptimeMillis);//第6行代码
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
}

看到这段代码的第5行, msg.target = this; msg.target 赋了值,值就是当前的Handler。再看到第6行代码,它将我们发送的消息加入了消息队列MessageQueue。那么一切就解释的通了。

handler发送消息就是将消息加入消息队列里面去,然后Loop.loop()方法将消息队列里面的消息不断的取出来进行处理,进行处理的方法就是:msg.target.dispatchMessage(msg)方法,代码如下:

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

可以看到最后哪行代码handleMessage(msg),它最终还是回调
handler的handleMessage方法来处理消息的,那么也就和我们的以前了解的不谋而合了。总体上可以用下面这张图概括:(子线程发消息到主线程的处理图)

072649e9005f7a8ce08aa418d91e4def (2).jpg

最后到这里,我们的源码就分析完毕了,但是最后要说的是还是要自己看一看源码,博客写的再详细,也不可能有源码详细,全面。

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

推荐阅读更多精彩内容