深入理解Handler、Looper、Messagequeue

转载请注明出处:http://www.jianshu.com/p/088503cf208e

上一篇总结了一下Handler的基本用法,但是对于其原理并不太清楚,这篇主要分析下其内部的原理。看一下其源码是怎么回事,从源码的角度理解Handler机制,Handler、Looper、MessageQueue之间的关系。


1 简介

先对这几个类做一下简单介绍。
Handler
线程间通信的方式,主要用来发送消息及处理消息。
Looper
为线程运行消息循环的类,循环取出MessageQueue中的Message;消息派发,将取出的Message交付给相应的Handler。
MessageQueue
存放通过Handler发过来的消息,遵循先进先出原则。
Message:消息,线程间通信通讯携带的数据。

例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程


2 Looper

源码路径:frameworks/base/core/java/android/os/Looper.java
Looper主要工作:

  • 自身实例的创建,创建消息队列,保证一个线程中最多有一个Looper实例。
  • 消息循环,从消息队列中取出消息,进行派发。

Looper用于为线程运行消息循环的类,默认线程没有与它们相关联的消息循环;如果要想在子线程中进行消息循环,则需要在线程中调用prepare(),创建Looper对象。然后通过loop()方法来循环读取消息进行派发,直到循环结束。

程序中使用Looper的地方:

  1. 主线程(UI线程)
    UI线程中Looper已经都创建好了,不用我们去创建和循环。
  2. 普通线程
    普通线程中使用Looper需要我们自己去prepare()、loop()。
    看一下普通线程中创建使用Looper的方式,代码如下:
class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                 // process incoming messages here
            }
        };
        Looper.loop();
   }
}

这段代码是Looper源码注释中给的典型列子,主要步骤:

  1. Looper 准备,(Looper实例创建);
  2. 创建发送消息、处理消息的Handler对象;
  3. Looper开始运行。

印象中在UI线程没有出现过Looper相关的东东,这是因为UI线程中会自动创建Looper对象并进行消息循环,我们不再需要调用Looper.prepare()和Looper.loop(),但是在子线程中如果想要创建使用Handelr则需要向如上所示。
我们通过源码看一下Looper实例创建的方法:

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

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构造方法是私有的,只能通过prepare()进行创建Looper对象。prepare()会调用私有方法prepare(boolean quitAllowed)。
第6行 sThreadLocal为ThreadLocal<Looper>类型变量,用来存储线程中的Looper对象。
prepare方法中首先判断sThreadLocal是否存储对象,如果存储了则抛出异常,这是因为在同一个线程中Loop.prepare()方法不能调用两次,也就是同一个线程中最多有一个Looper实例(当然也可以没有,如果子线程不需要创建Handler时)。
该异常应该许多朋友都遇见过,如在UI线程中调用Looper.prepare(),系统会替UI线程创建Looper实例,所以不需要再次调用prepare()。

接着看Looper的构造器:

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

在构造器中,创建了一个MessageQueue消息队列;然后获取当前的线程,使Looper实例与线程绑定。
由prepare方法可知一个线程只会有一个Looper实例,所以一个Looper实例也只有一个MessageQueue实例。但这并不代表一个线程只能有一个MessageQueue实例,这是为什么呢?很简单,我们可以自己new 一个MessageQueue实例就可以了,但这个MessageQueue并不是该线程中Handelr对应的消息队列。

接着看Looper的消息循环:

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //通过Looper实例获取消息队列
    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);
        }
        //将消息派发给target属性对应的handler,调用其dispatchMessage进行处理。
        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
        }
        msg.recycle();
    }
}

loop()函数是静态的,所以它只能访问静态数据。
第2行myLooper()函数也是静态的,其代码如下

return sThreadLocal.get()

获取sThreadLocal存储的Looper实例,如果为空则抛出异常,这也说明loop()方法必须在prepare()方法之后才能调用。
第7行 通过Looper对象获取消息队列。
然后进行消息循环,从队列中获取消息,把消息交给msg的target的dispatchMessage方法进行处理,也就是交给handler进行处理,这个稍后说Handler时再细说。
然后调用msg.recycle(); 释放msg。

Looper.loop()是给死循环,那如何终止消息循环呢?我们可以调用Looper的quit方法或quitSafely方法。
quit方法作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息还是非延迟消息。
quitSafely只会清空MessageQueue消息队列中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

总结:

  1. UI线程会自动创建Looper实例、并且调用loop()方法,不需要我们再调用prepare()和loop().
  2. Looper与创建它的线程绑定,确保一个线程最多有一个Looper实例,同时一个Looper实例只有一个MessageQueue实例。
  3. loop()函数循环从MessageQueue中获取消息,并将消息交给消息的target的dispatchMessage去处理。如果MessageQueue中没有消息则获取消息可能会阻塞。
  4. 通过调用Looper的quit或quitsafely终止消息循环。

3 Handler

源码路径:frameworks/base/core/java/android/os/Handler.java
Handler主要职责:

  1. 发送消息给MessageQueue(消息队列);
  2. 处理Looper派送过来的消息;
    我们使用Handler一般都要初始化一个Handler实例。看下Handler的构造函数:
public Handler() {
    this(null, false);
}

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

第8行 Looper.myLooper();获取当前线程保存的Looper实例,如果当前线程没有Looper实例则会抛出异常。这也就是说在线程中应该先创建Looper实例(通过Looper.prepare()),然后才可以创建Handler实例。
第13行 获取Looper实例所保存的MessageQueue。之后使用Handler sendMesage、post都会将消息发送到该消息队列中。保证handler实例与该线程中唯一的Looper对象、及该Looper对象中的MessageQueue对象联系到一块。

3.1 sendMessage

接着看一下平常使用Handler发送消息,先看sendMessage的流程:

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}

sendMessage最终调用到enqueueMessage函数,接着看下enqueueMessage。

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

在enqueueMessage中,首先设置msg的target属性,值为this。之前在Looper的loop方法中,从消息队列中取出的msg,然后调用msg.target.dispatchMessage(msg);其实也就是调用当前handler的dispatchMessage函数。
然后调用queue的dispatchMessage方法,将Handler发出的消息,保存到消息队列中。

3.2 post

看一下post方法

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

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

post方法中调用getPostMessage方法,创建一个Message对象,设置此Message对象的callback属性为创建Runnable对象。
然后调用sendMessageDelayed,最终和sendMessage一样,都是调用到sendMessageAtTime。调用enqueueMessage方法,将此msg添加到MessageQueue中。
也就是post(Runnable r) 并没有创建线程。其run方法是在Handler对应的线程中运行的。

3.3 dispatchMessage

这里主要说下handler是如何处理消息的。在Looper.loop方法中通过获取到的msg,然后调用msg.target.dispatchMessage(msg);也就是调用handler的dispatchMessage方法,看下Handler中dispatchMessage源码

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

在dispatchMessage方法中首先判断msg的callback属性,如果不为空则调用handleCallback函数,
handleCallback函数如下:

private static void handleCallback(Message message) {
    message.callback.run();
}

handleCallback函数中messag.callback也就是我们传的Runnable对象,也就是调用Runnable对象的run方法。
如果msg.callback属性为空,判断Handler属性mCallback是否为空, 不为空则让mCallback处理该msg。
mCallback为空则调用Handler的handleMessage,这就是我们创建Handler对象时一般都实现其handleMessage方法的原因。


4 MessageQueue

源码路径:frameworks/base/core/java/android/os/MessageQueue.java
MessageQueue 消息队列:

  • enqueueMessage将消息加入队列
  • next从队列取出消息
  • removeMessage移除消息

MessageQueue内部是如何管理这些消息队列的就先不说了,之后又空再好好分析一下。


5 总结

本文分析了下Handler、Looper、MessageQueue之间的联系,及handler进程间通信的原理。了解到Handler不仅仅可以更新UI,也可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理这些消息的代码都会在你创建Handler实例的线程中运行。

欢迎大家关注、评论、点赞
你们的支持是我坚持的动力。

欢迎关注我的微信公众号

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

推荐阅读更多精彩内容