Android消息机制系列(1)——Handler Looper Message源码解析

一、基本概念:

在谈到Android消息机制的时候,我们不可避免的要涉及要一下几个概念:
1、Message 消息
2、MessageQueue 消息队列
3、Handler 处理者?它的实际作用包括发送消息(sendMessage),处理消息(handleMessage)
4、Looper 消息循环,或者轮询器

从字面上来看,前两者都比较好理解。剩下的Handler、Looper,让我们先重点理解一下Looper:

二、Looper

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 prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

简单翻译过来就是:Looper用于管理一个线程的消息循环。 默认情况下,线程是没有相关联的Looper的,需要调用Looper.prepare()来创建,创建完成之后,Looper.loop()会不断地循环来处理消息,直到循环停止。一般情况下是和Handler来配合使用的。

使用Looper+Handler典型代码例如:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare(); //为本线程创建一个Looper
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
            // process incoming messages here
            }
        };
        Looper.loop(); //消息循环
    }
}

Looper重要属性:

// sThreadLocal.get() will return null unless you've called prepare()
static final ThreadLocal sThreadLocal = new ThreadLocal();
private static Looper sMainLooper;  // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;

其中,sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。

Looper的prepare()方法:

public static void prepare() {
    prepare(true);
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) { //一个线程只能对应一个Looper实例
    throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed)); //new一个Looper的实例保存在ThreadLocal中
}

可以看到,prepare()方法将new一个Looper的实例,同时将该Looper实例放入了ThreadLocal。并且当第二次调用时报错,说明prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例。

Looper的构造函数:

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

Looper构造函数中创建一个消息队列MessageQueue。同时也可以看到,一个Looper实例对应一个线程、一个MessageQueue

Looper的loop()方法:

public static void loop() {
    // myLooper() 返回的是sThreadLocal.get(),也就是sThreadLocal存储的Looper实例
    final Looper me = myLooper(); 
    if (me == null) {
        // 可以看到这里,如果在线程中没有调用Looper.prepare()的话Looper实例是空的
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on    this thread.");
    }
    final MessageQueue queue = me.mQueue; //拿到Looper实例对应的消息队列
    // 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;
    }
    ... ...
    msg.target.dispatchMessage(msg); //真正处理消息的地方
    ... ...
    // 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();
    }
}

可以看到,方法中无限循环体里面,不断地通过queue.next()来取出准备处理的消息。而真正处理消息的地方是在这一句:msg.target.dispatchMessage(msg)。target是什么?我们可以从Message源码中可以看到,target是一个Handler,也就是说,真正处理消息是该消息对应的Handler实例的dispatchMessage(msg)方法中。

小结:
1、Looper用于管理一个线程的消息循环。
2、由于默认情况下,线程是没有相关联的Looper的,因此必须调用Looper.prepare()来创建。
3、Looper.prepare()方法,会使得线程绑定唯一一个Looper实例
4、Looper创建时也会创建一个消息队列MessageQueue。
2、Looper.loop()方法,不断从MessageQueue.next()中去取出准备处理的消息,交给消息的对应的handler的dispatchMessage去处理。

简言之,Looper提供了存储消息的队列(MessageQueue),同时不断循环取出准备处理的消息。那么谁来发送消息和真正处理消息呢?——当然是我们熟悉的Handler了。

三、Handler:

从第一部分我们可以推测出,Handler的两个重要作用:
1、发消息到MessageQueue中
2、接受Looper分发的消息处理任务,真正处理消息

那么Handler是怎么和一个线程的MessageQueue、Looper实例关联上的呢?

这个要看一下Handler是怎样被new出来的。具体我会另开文分析,这里以开篇的例子来分析,当使用new Handler()来创建一个Handler的情况(意思是还有其他情况):

public Handler() {
    this(null, false);
}

最后是调用了这个:

  public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class 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();  //这里,关联上了当前线程的Looper实例
    if (mLooper == null) {
        throw new RuntimeException( 
        // 在一个线程里面,创建handler之前必须调用Looper.prepare()
        "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue; // 这里,取得当前线程的Looper实例的消息队列
    mCallback = callback;
    mAsynchronous = async;
}

可以看到,在这种情况下,在构造函数里面,通过Looper.myLooper()获取了当前线程保存的Looper实例,然后又获取了这个Looper实例中保存的消息队列MessageQueue。Handler实例就是这样和一个线程的MessageQueue、Looper实例一一关联上的。

这里的当前线程,是指创建Handler实例所使用的线程。

接下来就简单了,用Handler发送一条消息,不论使用何种方法,最终都会调到这个函数:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue; //当前线程对应Looper实例的消息队列
    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);
}

然后调用了这个函数:

private boolean enqueueMessage(MessageQueue queue, Message msg,long     uptimeMillis) {
    msg.target =this; //将消息的target设为当前的Handler实例
    if(mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis); //将消息插入到消息队列中去
}

重要的是这句:msg.target = this; //将消息的target设为当前的Handler实例

还记得第一部分结尾处,loop()方法中取出一条要处理的消息,然后调用msg.target.dispatchMessage(msg)嘛?那么现在我们知道了,msg的target就是在发这条消息的时候设置上的。以保证后续处理消息的时候,能找到处理这条消息的Handler。

queue.enqueueMessage(msg, uptimeMillis); //这句将设置好target等属性的消息放到消息队列中去

终于,让我们看一下msg.target.dispatchMessage(msg)的dispatchMessage()函数:

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

public void handleMessage(Message msg) {
}

这个方法最终调用我们在创建Handler时重写的handleMessage()方法或者callback来进行消息处理;

而handleMessage()我们就很熟悉了,每个创建Handler的都要重写handleMessage()方法,根据msg.what来处理消息,例如:

private Handler mHandler = newHandler() {
    public void handleMessage(android.os.Message msg) {
        switch(msg.what) {
            case:
            break;
            default:
            break;
        }
     };
};

三、总结

1、默认情况下线程是没有Looper实例的,Looper.prepare()为线程关联到唯一的Looper实例,以及Looper实例的MessageQueue。

2、Handler被创建时,会关联到“某个线程”的唯一的Looper实例和MessageQueue;Handler的主要作用是将处理消息切换到“这个线程”来处理。

3、当通过Handler发送一条Message时,该消息的target就被设为这个Handler实例(this)

4、Handler发送一条消息,实际上是将这条消息插入到对应的消息队列MessageQueue中

5、线程对应的Looper实例loop()方法来处理消息时,会根据这条消息的target(也就是Handler实例),回调Handler的dispatchMessage()方法进行处理。dispatchMessage()方法最终调用我们在创建Handler时重写的handleMessage()方法或者callback。

这里面,比较重要的是,明确Handler被创建时,和哪个线程或者和哪个线程的Looper相关联,这将决定了任务最后在哪个线程执行;

这里面有个特殊情况,就是在主线程创建Handler时不需要调用prepare()和loop(),因为这部分工作Android已经帮我们做过了,具体可以看一下ActivityThread的代码。

简单的说,一条消息从发出到处理经历了:

[Handler发送消息] -> [消息进入Looper的MessageQueue队列] -> [loop循环从MessageQueue取出要处理的消息] -> [Handler处理消息]


Reference:

Handler源代码

Looper源代码

Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

Android异步消息处理机制完全解析,带你从源码的角度彻底理解

Android的消息机制之ThreadLocal的工作原理

Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

Android异步消息处理机制详解及源码分析

Handler常见用法

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

推荐阅读更多精彩内容