Handler 机制源码分析

前言:很久之前就想写关于 Handler 消息机制的文章分析了,但是感觉内容比较多不太容易讲的明白,就暂时告了一段落了。现在开始写源码分析的文章,发现很多地方都用到了 Handler 这个东西,还是躲不过去啊,毕竟是安卓系统里面非常重要的一个内容。所以今天我就分析分析这安卓消息机制到底是如何运转的。

我将 Handler 消息机制的流程总结为五个阶段,这里以主线程的消息机制为例:

  1. 系统创建(创建主线程的 Looper、MessageQueue,并开启消息循环)

  2. 创建 Handler

  3. 创建 Message

  4. 通过 Handler 发送 Message

  5. 处理 Message

注:子线程的消息机制其实和主线程一样,不同的就是主线程第一步系统帮你做了,而子线程要自己手动去创建 Looper 并开启消息循环。

接着我会从上面的五个阶段一个个分析过来。相信每个安卓开发都使用过 Handler,不过为了大家方便对照理解,我先给出 Handler 最普遍的使用例子,然后利用这个例子来对上面的五个阶段进行源码分析。

public class MainActivity extends AppCompatActivity {
    Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 1.系统创建(创建主线程 Looper、主线程 MessageQueue)
        // 这一步不需要我们进行任何操作,因为系统自动帮我做了
        
        // 2.创建 Handler
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // TODO
                // 5.处理 Message
            }
        };
        new Thread() {
            @Override
            public void run() {
                // 3.创建 Message
                Message message = new Message();
                message.what = 1;
                // 4.利用 Handler 发送 Message
                mHandler.sendMessage(message);
            }
        }.start();

    }
}

1、系统创建

在我们运行一个 app 的最开始,安卓系统会进入到 ActivityThread 这个类中的静态 main 方法,学过 java 的同学应该知道,这就是一个应用程序的入口,所以我们的应用也是从这里开始的。

public final class ActivityThread extends ClientTransactionHandler {
        
    public static void main(String[] args) {
        
        ...// 仅贴出关键代码
            
        Looper.prepareMainLooper();

        Looper.loop();
    }
}

可以看到第七行,这里使用了 Looper.prepareMainLooper(),就是这一句话完成了主线程 Looper、MessageQueue 的创建。贴出它的代码看看:

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

看到第二行,这里使用了 prepare(false) 去初始化 Looper 和 MessageQueue。然后再第七行,使用 myLooper() 获取刚刚创建完成的 Looper。具体如下所示:

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

看到第二行,这里先利用 sThreadLocal.get() != null 去判断这个线程是否已经拥有一个 Looper。这里的 sThreadLocal 是 ThreadLocal 类的一个对象,ThreadLocal 是一个范型类,这里的范型就是 Looper,通过调用它的 get 方法能够获得 Looper 对象。后面使用 myLooper() 去获取 Looper 也是通过 sThreadLocal.get() 方法。具体 ThreadLocal 的工作原理可以参考 Android的消息机制之ThreadLocal的工作原理

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

可以看到,这里先获取了当前线程的 Looper 和 MessageQueue。然后就是一个 for 循环,在第十六行使用 queue.next() 去 MessageQueue 中不断取消息。由于 queue.next() 也是一个循环,所以当没有消息时,这一步就会阻塞,直到取出消息。因为我们这里是主线程,所以这个 MessageQueue 永远不会返回 null,也就不会 msg ==null 这个判断 return 了。拿到消息,接着就把消息分发给 Handler 处理。最后在第二十七行回收这个消息。

我们先看看 MessageQueue 的 next 方法做了什么:

public final class ActivityThread extends ClientTransactionHandler {
        
    public static void main(String[] args) {
        
        ...// 仅贴出关键代码
            
        // 这一步已经分析过了(创建了 Looper、MessageQueue)
        Looper.prepareMainLooper();

        // 开启消息循环
        Looper.loop();
    }
}

再创建了主线程 Looper 和 MessageQueue 之后,系统就使用 Looper.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.");
        }
        // 获取当前线程的 MessageQueue
        final MessageQueue queue = me.mQueue;

        for (;;) {
            // 从 MessageQueque 中取消息
            // 由于 MessageQueque.next() 也是一个循环,所以如果没有消息就阻塞了
            Message msg = queue.next();
            
            // 主线程这里的消息永远不会为 null,因为 MessageQueue 不会被 quit
            if (msg == null) {
                return;
            }

            // 消息的分发
            msg.target.dispatchMessage(msg);
           
            // 消息的回收
            msg.recycleUnchecked();
        }
    }

可以看到,在第六行和第十一行分别获取了当前线程的 Looper 和 MessageQueue。然后就是一个 for 循环,在第十六行使用 queue.next() 去 MessageQueue 中不断取消息。由于 queue.next() 也是一个循环,所以当没有消息时,这一步就会阻塞,直到取出消息。因为我们这里是主线程,所以这个 MessageQueue 永远不会返回 null。然后在第二十四行的消息分发给 Handler 处理。最后在第二十七行回收这个消息。

我们先看看 MessageQueue 的 next 方法做了什么:

Message next() {
        ...// 仅贴出关键代码
        for (;;) {
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null) {
                    if (now < msg.when) {
                        // 如果还没到处理消息的时候,就继续循环
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now,                                                                         Integer.MAX_VALUE);
                    } else {
                        // 把这个消息取出来返回给 looper 进行处理
                        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;
                }

                // 如果使用了 quit,这里就会返回 null,然后当前线程的 looper 循环也就结束了
                if (mQuitting) {
                    dispose();
                    return null;
                }
        }
    }

如上代码所示,我在关键的地方都已经标出注释了,然后再稍微解释一下。首先这个 next() 方法是一个循环,它会不断的从消息队列中取消息。如果消息队列中的第一个消息不为 null,并且到了处理这个消息的时间,那么就把这个消息移出消息队列,返回给 looper 进行处理。否则就一直进行循环直到取出消息或者使用了 quit() 方法停止了当前 looper 循环。

接着我们回到 looper 方法,看看这个 next() 返回消息之后,是怎么进行消息分发的。

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 1.使用 post 方法发送消息,会在这里执行它的 run 方法
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                // 2.使用 Handler(new Handler.Callback())  创建的 handler 对象,
                // 会在它的回调函数 handleMessage 内处理消息
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 3.使用 Handler() 创建的对象通过 sendMessage 方法发送消息,会在 handler 的                       
            // handleMessage 方法内处理消息
            handleMessage(msg);
        }
    }

可以看到,这里有三种方式去处理消息,而且是有优先级的,具体哪一种取决于你创建 Handler 对象的姿势和发送 Message 的姿势,这个相信大家都清楚,我上面也给出注释了,就不再解释了。

总结一下,在我们 app 启动的入口,系统会自动帮我们初始化主线程 Looper,并开启 Looper 循环,它会不断的从 MessageQueue 中取出消息,然后通过分发消息去处理消息。并且主线程的 Looper 是无法停止的,而子线程的 Looper 可以通过调用 quit 方法去停止循环的,子线程的消息分发机制与主线程不同之处就是需要手动去创建 Looper 并开启 Looper 循环。

2、创建 Handler

在最上面给出的例子中,我们使用 new 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 " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
    }

可以看到,在我们使用这个没有参数的构造方法之后,它又调用带两个参数的构造方法。在第八行,先获取到了当前线程的 Looper,然后在第九行判断这个 Looper 是否存在,由于我们主线程的 Looper 系统已经创建了,所以这是判断为 false,继续往下,在第十四行获取当前线程的 MessageQueue。

总结一下,在我们调用 Handler 的无参构造方法时,这个 handler 对象就会绑定当前线程的 Looper 和 MessagQueue。

如果你要在子线程中使用 Handler 时,要先使用 Looper.prepare() 去初始化子线程的 Looper,否则会抛出异常。还有记得要使用 Looper.loop() 去开启消息循环,不然你的子线程 Handler 是收不到消息的。

3、创建 Message

在示例中我们通过 new Message() 的方式创建了一个 Mesage,然后可以为这个 Message 的 waht 赋值来进行不同消息的区分,我们还可以通过为它的 obj 赋值来传递 Object 对象。

当然了,有一种更好的方式来创建 Message,通过使用 Message.obtain() 来复用之前的 Message,以此来减少创建一个新的 Message 带来的系统开销。

总结一下,通过 new Message() 或者 Message.obtain() 来获得一个 Message,然后可以为 Message 设置各种参数来传递给 Handler。

4、发送 Message

在示例中,我们使用 Handler.sendMessage(message) 去发送消息,最终它会调用 Handler 的 enqueueMessage 方法,将消息存入消息队列,如下所示:

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

可以看到,第二行的 msg.target 就是我们主线程创建的 mHandler,第六行通过 queue.enqueueMessage 将我们创建的消息存入了消息队列。

boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            if (mQuitting) {
                // 如果已经调用过了 quit,这里就会抛出异常
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            // mMessages 可以理解为消息队列的第一个消息
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 如果消息队列中没有消息或者这个消息的执行时间要被队列首消息的执行时间还要早
                // 就把这个消息插入到消息队列的头部
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 否则就从消息队列的头部往后排,直到后面没有消息了或者此消息的执行时间比那个消息的执                 
                // 行时间早,就插入到那个消息之前
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }
        }
        return true;
    }

可以看到,这个方法就是消息的入队方法,通过比较各个消息的执行时间,来进行排序从小到大的排序,执行时间在前的就在队列的前面。这里的算法是单链表的插入算法,大家可以通过自己画图模拟来理解。

另外,我们除了使用 Handler.sendMessage(message) 方法去发送消息外,还可以使用 Handler.post(new Runnable()) 去发送消息。这里其实就是把这个 Runnable 对象组装成一个 Message 去存入消息队列,最后在分发事件的时候,不再通过 handleMessage 处理,而是执行它的 run() 回调方法去处理罢了,对我们理解消息通信机制来说没影响。

总结一下,通过调用 Handler.sendMessage(message) 或者 Handler.post(new Runnable()) 方法,可以把一个消息从子线程传递到 Handler 所在线程的 MessageQueue,也就是这一步通过 Handler 实现了线程的切换。

5、处理 Message

在示例中,我们使用 handleMessage(Message msg) 来处理消息,这是最后一步了,没有具体源码分析,在第一个流程系统创建 Looper 中里,我们讲到了 looper 中会使用 dispatchMessage 方法取分发消息。

那么让我们回顾一下它是怎么收到消息的。首先我们通过创建 Message,然后用主线程的 Handler 发送这个消息到 MessageQueue,然后 Looper 循环不断的从 MessageQueue 中取消息,取到消息之后就通过消息分发,将这个 Message 作为参数传递给 handleMessage 进行处理。


全文总结

我们来回顾一下 Handler 消息机制的几个流程,并给出需要注意的地方:

  1. 创建 Looper 并开启消息循环
    • 主线程不需要此步骤,因为系统自动创建并开启了 Looper。
    • 子线程需要手动调用 Looper.prepare() 去创建 Looper,并调用 Looper.loop() 开启消息循环。
  2. 创建 Handler 对象
    • 若想要在主线程接收消息,这个 Handler 对象就需要在主线程创建。
    • 若想在子线程接收消息,就需要在子线程创建 Handler 对象,记得在创建 Handler 对象之前创建 Looper,否贼就会抛出异常。
  3. 创建 Message
    • 可以使用 Message.obtain() 来获取 Message 对象,来减少系统创建对象的开销。
  4. 发送 Message
    • 可以使用 sendMessage 系列方法去发送消息,也可以使用 post 系列方法去发送消息。post 系列方法最终也是通过 sendMessage 系列方法去实现消息入队的。不同之处在于处理方法的回调不同。
  5. 处理 Message
    • 如果是通过 sendMessage 系列方法发送的消息,最终会在 handleMessage 中处理。如果是通过 post 系统方法发送的消息,最终会在 run 回调中处理。

最后,这篇文章分析 Handler 消息机制的文章也算结束了。文章在源码分析的一些小细节上没有很深入,因为我觉得如果每个细节都贴出来的话,文章比较容易偏离主题,看了细节代码再回到主线去讲解,让读者分不清主次。然后文章也是我站在巨人的肩膀上外加自己阅读源码调试代码得出的结论,如果有不对的地方千万千万要指出。然后我再抛出一个疑问:Android ActivityThread 的 main 方法是什么时候被调用的?希望自己以后也能够解答一下这个问题。

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

推荐阅读更多精彩内容