深入理解Android消息机制之Handler,Looper,MessageQueue

前言

重新研究了Android Handler,Looper,MessageQueue的源码,收获很多,这里记录一下。文中会提几个问题,来更好的引起思考。

正文

说到用来线程间通信的Handler机制,我们就会谈及Looper和MessageQueue。简单来说,Looper就是一个循环器,每一个线程都只有自己的一个Looper对象,而每一个Looper对象也都会绑定一个消息队列,Looper的任务就是负责循环读取这个消息队列。如果有消息就交给Handler处理,然后Handler调用handleMessage回调到主线程去更新UI,而如果队列为空则阻塞等待消息返回。而且,Handler的任务除了刚才说的处理消息更新UI,还负责在其他线程中发送消息到消息队列中。下面一张引用他人的图:

图解
图解

注意左上角的Handler持有的引用是主线程的,是主线程把Handler对象的引用传递给其他线程的。

以上的东西相信都能理解,下面提几个问题。
1、如何做到每个线程只有一个looper对象?
这里要引出ThreadLocal的概念了。Looper中有一个成员变量sThreadLocal。

 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

ThreadLocal不是线程,它是一个关于创建线程局部变量的泛型类,使得各线程能够保持各自独立的一个对象,而其他线程访问不到。具体的做法是通过在初始化Looper的时候调用sThreadLocal.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);  
}     

即获取当前的线程的ID,为线程实例化一个Looper并存储到这个线程。所以,每一个线程有维持着一个values(里面是一个table数组),放着不同于其他线程的一个Looper,通过ThreadLocal的set和get方法可以访问(Handler的实例化其实内部是调用了sThreadLocal.get()去获取此线程的Looper,然后通过send方法就可以往这个Looper所绑定的消息队列中放消息了)。不同的线程中虽然访问的是同一个ThreadLocal的set和get方法,但它们对ThreadLocal所做的读写操作都只是在各自线程的内部。这样就做到了一个线程拥有唯一一个Looper。

另外,非UI线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper,并显式调用prepare和loop方法。而UI线程,其实就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

2、消息队列是以队列的数据结构存储消息?
其实消息队列的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息的,Message对象内部包含一个next变量,该变量指向下一个消息对象。
原因是队列的方式是先进先出,而消息队列中消息是按时间排序的。所以使用链表实现才能更快的在任意位置插入消息,复杂度O(1)。

3、消息队列具体怎么放消息,取消息?
放消息:
MessageQueue对象通过调用enqueueMessage()放消息,首先会判断一下,如果新添加的消息的执行时间when是0,或者其执行时间比消息队列头的消息的执行时间还早,就把消息添加到消息队列头(也就是链表头),否则就找到合适的位置将当前消息添加到消息队列。最终,消息队列中的消息是按when从小到大排序的(when最小的消息在链表头)。

取消息:
取消息是在next()方法中,首先找到消息队列头部的消息或者第一个异步消息,如果当前时间小于此Message的when,就计算时间差并赋值给nextPollTimeoutMillis(到达时间再进行唤醒),否则返回此消息给Looper。
next()是MessageQueue中的一个非常重要的函数,简单来说,它的任务就是获取消息队列中的下一个消息,然后并返回给Looper。下面深入到MessageQueue next方法中:

  Message next() {
        //mPtr代表c++层NativeMessageQueue对象
        final long ptr = mPtr;
        if (ptr == 0) {//表示消息循环已经退出,直接返回
            return null;
        }
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;//下一次执行的时间
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);//在这个方法中有可能会发生线程空闲等待,即当nextPollTimeoutMillis=-1时,表示没有消息,或者当nextPollTimeoutMillis>0,即没有到达下一条消息的执行时间。这两种情况下都会调用到C层的epoll_wait函数来等待管道中有内容可读的,直到在enqueueMessage中有新消息入队,此时会调用nativeWake(mPtr)往管道的写入一个字符串从而去唤醒此线程.
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    //查询MessageQueue中的下一条异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        //设置下一次轮询消息的超时时间
                        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 {
                    // No more messages.
                    //没有消息
                    nextPollTimeoutMillis = -1;
                }
                // Process the quit message now that all pending messages have been handled.
                ////消息正在退出,返回null
                if (mQuitting) {
                    dispose();
                    return null;
                }
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                //执行到这里意味着没有消息或者消息的执行时间还没到达。在进入空闲等待状态前,如果应用程序注册了IdleHandler接口来处理一些事情,那么就会先执行这里IdleHandler,然后再进入等待状态
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            //执行这些注册了的IdleHanlder
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;
            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            //重置nextPollTimeoutMillis为0,因为有可能在执行IdleHandler的时候,已经有新的消息加入到消息队列中去了
            nextPollTimeoutMillis = 0;
        }
    }

什么时候会进入阻塞状态?
有两种情况,一是当消息队列中没有消息时,它会使线程进入等待状态;二是消息队列中有消息,但是消息指定了执行的时间,而现在还没有到这个时间,线程也会进入等待状态。


下面提炼Handler,Looper,MessageQueue的整个设计,可以更好的理解整个消息机制

1、首先是Looper

public class Looper {
    /**
     * 每个线程都只有一个Looper对象
     */
    static ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
     //消息队列
    MessageQueue mQueue;
    Looper(){
        mQueue = new MessageQueue();
    }
    /**
     * 实例化一个Looper,并存储到该线程。
     */
    public static void prepare(){
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new 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.");
        }
        while (true){
            // 获取下一个Message,只有当存进去null的时候才会返回null
            // 而当队列为空时,会阻塞直到队列有Message再返回
            Message msg = mQueue.next();
            //如果取到null,就退出
            if (msg == null)return;
            //交给Message的目标Handler去处理
            msg.target.dispatchMessage(msg);
        }
    }
    /**
     * 获取当前线程的Looper
     */
    public static Looper myLooper(){ return sThreadLocal.get();}
}

2、接着是消息队列

public class MessageQueue {
    /**
     * 消息队列,Android源码自己实现了线程安全,这里简单模拟原理
     */
    private LinkedBlockingDeque<Message> queue = new LinkedBlockingDeque<>();
    private final Object lock = new Object();
    
    /**
     * 返回一个message
     */
    Message next(){
        while (true){
            if (queue.peek() !=null){
                //如果有Message,直接返回
                return queue.poll();
            }else {
                //如果没有Message,则阻塞,等待加入新Message时唤醒
                synchronized (lock){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    /**
     * 向队列加入一个Message,并唤醒前面的wait
     * 这个函数可以在任意线程调用
     */
    void enqueueMessage(Message message){
        synchronized (lock){
            queue.push(message);
            lock.notify();
        }
    }
}

3、Message的定义

public class Message {

    //属性
    public int what;
    public int arg1;
    public int arg2;
    public Object obj;

    //可以把Runnable接口封装到消息
    Runnable callback;
    /**
     * 目标Handler
     */
    Handler target;

    public Message(Runnable callback, int what) {
        this.callback = callback;
        this.what = what;
    }
}

4、最后是Handler

public class Handler {
    private Looper looper;
    final Callback mCallback;

    public interface Callback {
        boolean handleMessage(Message msg);
    }

    public Handler(){
        this(null);
    }

    public Handler(Callback callback) {
        looper = Looper.myLooper();
        mCallback = callback;
    }
    
    public void post(Runnable runnable){
        send(new Message(runnable,0));
    }

    //向Looper的消息队列中发送Message
    public void send(Message message){
        looper.mQueue.enqueueMessage(message);
    }

    //在此线程中处理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();
    }
    //子类需要实现此方法去处理消息
    public void handleMessage(Message msg) {
    }
}

总结

分析完源码后感觉自己对消息机制的理解更深了一点,或者说印象更深刻了。很多时候遇到不懂的东西从源码入手会有一种豁然开朗的感觉。以上如有误解之处,请指出。

参考

Android应用程序消息处理机制(Looper、Handler)分析

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

推荐阅读更多精彩内容