从源码角度分析 Handler Looper MessageQueue

博客原文地址:http://jockio.github.io/2016/08/16/source-code-of-handler-and-looper/

前言

首先我们要知道,创建Handler之前,要先创建与之配套的Looper。
在主线程中,系统已经初始化了一个 Looper 对象,因此程序直接创建 Handler 对象即可,然后就可以通过 Handler 来发送、处理消息了。
在子线程中,必须自己创建一个 Looper 对象,并启动它。
创建 Looper 对象调用它的 prepare 方法即可(prepare 方法保证每个线程最多只有一个 Looper 对象),然后调用 Looper 的静态 loop 方法来启动它。loop 方法使用一个死循环不断地从MessageQueue 中取消息,并将取出的消息分发给该消息对应的 Handler 处理。至于它们具体做了哪些事,我们会在后面详细讲述。

Looper的创建

我们先看一下Looper的构造函数

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

首先我们注意到该构造方法是被private修饰的,也就是说我们无法通过new的方式来创建Looper。其次,我们可以从Looper的构造方法中看出,在创建Looper的时候,创建了与之配套的MessageQueue,然后获取了创建当前Looper线程的引用。
而要想创建Looper,只需调用Looper.prepare();。该方法保证了一个线程只能创建一个与之相关联的Looper,并且将创建出的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));
}

Handler的创建

Looper对象成功创建之后,我们再来看看Handler。当我们调用Handler的无参构造函数创建Handler时,它内部调用了另一个重载的构造方法this(null, false)

public Handler(Callback callback, boolean async) {
    ...//something we just ignore

    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及MessageQueue的引用。

Handler 发送消息

创建完Handler之后,我们先从handler.sendMessage()说起:

public class MainActivity extends Activity{
    protected void onCreate(Bundle onSavedInstanceState){
        super.onCreate(onSavedInstanceState);
        setContentView(R.layout.main);
    
        MyRunnable mRunnable = new MyRunnable();
        Thread mThread = new Thread(mRunnable);
        mThread.start();
    }
  
    public static Handler = new Handler(){
        @Override
        public void handleMessage(Message msg){
            //do some magic
        }
    };
  
    static class MyRunnable implements Runnable{
        @Override
        public void run(){
            Message msg = Message.obtain();
            //do some magic
            handler.sendMessage(msg);
        }
    }
}

而不管我们调用sendEmptyMessage()或者是sendMessage(),最终都会走到这里:

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

关键是最后return时,调用了enqueueMessage(),我们一起看一下该方法的具体实现:

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

enqueueMessage的方法实现可以看出,要发送的消息获取到了当前Handler的引用,也就是msg.target 。然后这条信息被加入到了与当前线程相关联的MessageQueue中。到此,发送消息的逻辑已经结束。
那么Handler处理消息的方法又是在什么时候回调的呢?要弄明白这一点,我们要对Looper.loop()进行分析。

Looper.loop();

前面我们也已经提过,要创建一个Handler其实是需要三个步骤的:

  1. 调用Looper.prepare();
  2. 创建Handler
  3. 调用Looper.loop();

    前面两步我们已经讲解过了,那你肯定会好奇,loop()中到底做了什么呢?我们一起来看一下。
public static void loop() {
    //获取与当前线程相关联的Looper对象
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //获取与Looper对象相匹配的MessageQueue
    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;
        }

        ...//something we just ignore

        msg.target.dispatchMessage(msg);

        ...//something we just ignore

        // Mark the message as in use while it remains in the recycled object pool. Clear out all other details.
        msg.recycleUnchecked();
    }
}

从源码可以看出,loop()其实调用了一个死循环,不断的从与Looper配套的MessageQueue中取消息,然后调用msg.target.dispatchMessage(msg);进行消息的分发。
前面Handler发送消息的时候,我们已经分析过,每个要发送的Message都获取到了发送它的Handler的引用,也就是msg.target。因此这里msg.target.dispatchMessage(msg);其实也就是调用了handler的dispatchMessage进行消息的分发。

Handler的handleMessage()何时被回调?

我们一起来看一下handler的dispatchMessage():

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

很明显,就是在这里,Handler的handleMessage被回调了。
综上,我们可以说,是在Looper.loop()中,当循环不断的从MessageQueue中获取消息时,间接调用了Handler的handleMessage()方法。

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

推荐阅读更多精彩内容