Handler、Looper、MessageQueue详解---配合面试题食用味道更佳

Handler

一、成员与构造函数

Handler有一个静态成员值得注意:

private static Handler MAIN_THREAD_HANDLER = null;   

不出所料,有一个方法与其搭配:

public static Handler getMain() {
    if (MAIN_THREAD_HANDLER == null) {
        MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
    }
    return MAIN_THREAD_HANDLER;
}  

这里直接将其Looper设置为了MainLooper

构造函数

Handler的构造函数之间有回调关系,最终都会分别调用两种构造函数:

public Handler(Callback callback, boolean async) {
    ........
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}  

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}  

所以如果Looper对象不传递的话,Handler会自己调用Looper.muLooper()获取Handler所在线程绑定的Looper对象

二、Message的分发

Handler通过dispatchMessage分发Message:

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

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

可见,处理Message的优先级为:

  • Handler的handleCallback优先级最高
  • Handler的Callback回调第二优先级
  • Handler的handleMessage方法优先级最低

三、Message的产生

Handler本身有很多生成Message的方法:

public final Message obtainMessage()
{
    return Message.obtain(this);
}  

public final Message obtainMessage(int what)
{
    return Message.obtain(this, what);
}  

public final Message obtainMessage(int what, Object obj)
{
    return Message.obtain(this, what, obj);
}  

public final Message obtainMessage(int what, int arg1, int arg2)
{
    return Message.obtain(this, what, arg1, arg2);
}  

public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
{
    return Message.obtain(this, what, arg1, arg2, obj);
}  

平时都说,推荐使用Message.obtain()和Handler对象的obtainMessage方法来创建Message对象,且二者等价,原因就在于handler对象的obtainMessage方法都是调用的Message.obtain()系列

注意:在Message.obtain方法中会将其target属性初始化为Handler本身

四、Message消息的发送

Handler中有很多方法可以发送消息,主要分为两种:post和send

1. Post系列方法

这种方法都是直接post一个Runnable对象,所以首先来看看如何将一个Runnable对象封装成一个Message:

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

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

很简单,就直接创建一个Message对象,然后将Runnable设置为Message的callback属性

Q:post生成的Message没有将handler对象绑定到其target属性中?
对于该情况下的target赋值发生在handler的enqueueMessage方法中

现在我们来看post系列的方法:

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

public final boolean postAtTime(Runnable r, long uptimeMillis)
{
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
{
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}    

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}    

public final boolean postDelayed(Runnable r, Object token, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r, token), delayMillis);
}  

public final boolean postAtFrontOfQueue(Runnable r)
{
    return sendMessageAtFrontOfQueue(getPostMessage(r));
}  

可以看到,Post方法都是回调的sendMessage系列方法

2. send系列方法

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}  

public final boolean sendEmptyMessage(int what)
{
    return sendEmptyMessageDelayed(what, 0);
}  

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}  

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
}  

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}      

可见,大部分send方法都是调用的sendMessageDelayed方法,而sendMessageDelayed方法又是调用的sendMessageAtTime方法实现的,所以我们可以说,Handler中所有发送Message的方法的实现都是sendMessageAtTime

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

而sendMessageAtTime的实现实际上就是回调了enqueueMessage

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

最终我们搞清楚了,原来Handler中所有的回调,都最终归于queue.enqueueMessage方法

而Handler中还有一个比较奇葩的方法:

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    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, 0);
}    

这个看起来和sendMessageXX没啥不同啊,仔细发现,sendMessageDelayed在回调sendMessageAtTime的时候第二个参数做了手脚:

return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)  

其在delayMillis基础上加了SystemClock.uptimeMillis(这个意义是手机从开机到当下的时间间隔),所以其永远都是一个大于0的数;而sendMessageAtFrontOfQueue则把第二个参数直接置为0,实际上其效果等同于:sendMessageAtTime(msg, 0)

注意:1. MessageQueue在执行enqueue的时候会根据这个msg.when进行插入,所以该值越小越先被MessageQueue.next方法返回,从而越先被Handler处理。 2. 为什么选择使用SystemClock.uptimeMillis而不是System.currentMillis是因为当我们手动更改系统时间的时候会影响后者的值,导致MessageQueue在入队插入的时候逻辑混乱

3. 小结

粗略下来,感觉post和send实现都一样,为啥要有两种api发送Message,虽然实现机制都一样,但是在处理的时候就不一样了。还记得handler的dispatchMessage中处理消息的优先级吗?没错:post系列方法的getPostMessage中将post的参数Runnable赋值给了Message的callback,所以导致在处理Post的消息的时候是通过最高优先级的handleCallback方法处理的;而send系列方法,在通常情况下是通过handler的callback属性或者handleMessage方法处理消息。

五、经典面试题

Q1:子线程中创建Handler中报错是为什么?

在Handler的构造器中有这么一段:

mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }  

因为没有执行Looper.prepare()导致Looper.mylooper返回为null

Q2:如何在子线程创建Looper?

Looper.prepare()

Q3:为什么能通过Handler实现线程切换?

纯属个人理解:Handler的MessageQueue来自于Looper中的MessageQueue,而在Message.obtain中,Message的target被赋值为了Handler对象;Looper在从MessageQueue中取出Message后通过message.target来处理消息,所以消息的处理是在Looper所绑定的线程,而Handler可以在任意线程往MessageQueue中发送消息,所以实现了线程的切换

Q4:Handler处理消息发送在那个线程,是Handler所在的线程还是Looper?

Looper所在的线程

Q5:Looper和Handler一定要处于一个线程吗?子线程中可以用MainLooper去创建Handler吗?

不一定位于一个线程;子线程可以通过MainLooper创建Handler:实现有两种:

Handler h = new MyHandler(Looper.getMainLooper());  
Handler h = Handler.getMain();  // Handler的api  

Q6:Handler的post/send()的原理

最终都是调用的sendMessageAtTime -> enqueueMessage -> mQueue.enqueue

Q7:Handler的dispatchMessage()分发消息的处理流程?

优先级为:

  • 如果msg.callback不为空,调用handleCallback
  • 如果mCallback属性不为空,则调用mCallback.handleMessage
  • 最后调用handleMessage方法

Q8:Handler为什么要提供Callback构造方法?

正常使用Handler要使用一个子类继承Handler,然后再子类中实现handleMessage方法,而使用Callback构造函数则可以简化这一流程,一步到位。

Looper

一、成员和构造器

Looper的成员有三个需要关注:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class    
final Thread mThread;

其中的sThreadLocal就是实现Looper和线程绑定的原因

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

构造器中直接初始化了mThread和mQueue

二、prepare()、myLooper()方法

该方法大家都比较熟悉:

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对象,并且把它保存到了ThreadLocal中

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

myLooper()方法就是将Looper对象从ThreadLocal中取出来

三、quit和quitSafely

二者的区别就在于:mQueue.quit参数是否为true

public void quit() {
    mQueue.quit(false);
}  

public void quitSafely() {
    mQueue.quit(true);
}  

通过注释我们知道第一个方法可能导致还有没有接受到的Message,可能会产生意外情况,所以推荐我们使用第二个方法

四、loop方法

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;
    ....

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...

        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...

        msg.recycleUnchecked();
    }
}  

这里只保留了关键的代码:

  • 要想loop结束只有msg为null,而在MessageQueue中只有调用了quit方法才会导致其next方法返回null;而Looper的quit和quirSafely方法内部就是回调了mQueue的quit方法,所以只有调用Looper的quit或者quitSafely方法才会终止loop()方法
  • msg.target.dispatchMessage(msg)中,msg的target即为handler对象,所以这里就实现了跨线程

五、经典面试题

Q1:如何开启消息循环?

Looper.loop()

Q2:Looper两个退出方法有何区别?

quit会导致还未接收到的Message无法处理
quitSafely不会

Q3:如何终止消息循环?

quitSafely

Q4:Looper的loop流程

  • 获取Looper和MessageQueue
  • 无限for循环取出msg
  • 执行msg.target.dispatchMessage

Q5:MessageQueue的next什么情况下返回null

执行了quit或者quitSafely

Q6:Android如何保证一个线程最多只能有一个Looper?

通过ThreadLocal保证只有一个Looper

MessageQueue

这里需要提一下其Message mMessages代表链表的表头

我们这里只讨论其enqueueMessage方法和next方法。

1. enqueueMessage方法

boolean enqueueMessage(Message msg, long when) {
    。。。
    synchronized (this) {
        
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

       is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}  

可以发现,这里就是按照when的大小,将Message插入到链表中

2. next方法

Message next() {
   ...
    int nextPollTimeoutMillis = 0;
    for (;;) {
        ...
        //根据我们上面的分析,nextPollTimeMillis为0则不阻塞,也就是第一次循环不阻塞
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //这里判断msg不为空但是target为空,而我们enqueueMessage的时候特意设置了target的
            //所以这里的msg不是我们设置而是系统在初始化的时候设置的屏障,这里不再详解
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            //这里是正常情况下的msg
            if (msg != null) {
                //未达到处理时间的,将会计算需要等待的时间,不超过整形的最大值
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                 //可以处理的则直接取出后返回
                 } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
             //这里是队列中没有消息
            } else {
                nextPollTimeoutMillis = -1;
            }
            //上面分别对消息队列进行判断然后修改nextPollTimeoutMillis,而之前的分析可以看出这个值就是线程
            //需要阻塞的时长,有未达到处理时间的消息则阻塞对应时间,没有消息则一直阻塞直到被唤醒
        ...
        }
        ...
    }       
}  

next方法详细分析:MessageQueue的next方法详解

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

推荐阅读更多精彩内容