Android中的消息系统————Handler,MessageQueue与Looper

我们都知道,Android系统强制要求我们将更新ui等操作放在主线程中进行,而网络请求,读取文件等耗时操作则通常会放到子线程中运行,因此,在Android开发中经常需要在不同的线程之间进行切换。而Android系统为我们提供了消息系统来进行异步消息的处理,因此我们有必要了解一下Android消息系统的工作原理。

Handler,MessageQueue与Looper之间的关系

我们先来看一下Handler,MessageQueue与Looper三者之间的关系。首先,Handler对象负责发出一个消息,这个消息最终会被提交到一个MessageQueue之中,这个MessageQueue则是一个专门用来存储Message的队列集合。而Looper对象内部有一个无限循环,它会不断的从这个MessageQueue中取出消息,并将其交给发出该消息的Handler进行处理。

需要注意的是,我们可以在一个线程中创建很多个Handler对象,但是每个线程只会对应一个Looper和MessageQueue对象。Handler对象在初始化的时候会和该线程所对应的Looper及MessageQueue对象进行绑定。因此,Handler在哪个线程中创建,它发出的消息最终就会在哪个线程中执行。接下来我们通过源码来详细的看一下它们的工作原理。

消息系统的创建

先从消息系统的创建说起。Android中主线程的消息系统会在主线程启动时默认被创建,而子线程的消息系统默认则不会被创建,我们需要在子线程中手动调用Looper.prepare()和Looper.loop()这两个静态方法才可以开启子线程的消息系统。接下来我们以主线程为例看一下消息系统的创建过程。

Android主线程的消息系统是在ActivityThread类的main方法中被创建的,我们看一下main方法的代码:

    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper(); //先调用prepareMainLooper方法

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();  //然后调用loop方法

        throw new RuntimeException("Main thread loop unexpectedly exited");//因为Looper.loop()实际上是执行了一个无限循环,所以一般情况下不会走到这句,除非出现异常导致循环中断
    }

我们可以看到,与子线程不同的是,主线程的消息系统在启动时调用的是Looper.prepareMainLooper方法而非prepare方法。在调用完prepareMainLooper方法之后又调用了Looper.loop方法。我们看一下prepareMainLooper方法:

    public static void prepareMainLooper() {
        prepare(false);//调用prepare方法
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();//通过myLooper方法将创建好的looper对象赋值给sMainLooper全局对象
        }
    }

可以看到prepareMianLooper方法中其实也是调用的prepare方法,prepare方法的源码如下:

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

在prepare方法中,系统直接通过new关键字创建了一个Looper对象,并将这个Looper对象放在了一个名为sThreadLocal的全局对象中。

这个sThreadLocal对象是一个定义在Looper中的类型为ThreadLocal<Looper>的全局对象,并且被static final所修饰。ThreadLocal是java所提供的一个类,我们可以通过ThreadLocal的set(T value)方法来给这个ThreadLocal对象设置一个变量,但值得注意的是,通过ThreadLocal来维护的变量是线程私有的,各个线程通过ThreadLocal.get()方法取得的对象都是独立的,他们之间的操作都互不影响的。主线程将一个Looper对象设置给了一个ThreadLocal,其他子线程是无法通过这个ThreadLocal对象来获取主线程的Looper对象的。因此,Android通过ThreadLocal来维护Looper对象,就做到了每个线程对应一个独立的Looper对象。

我们再来看一下Looper的构造方法:

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

可见Looper对象在初始化时直接创建了一个MessageQueue集合,并赋值给成员变量mQueue。因此MessageQueue对象被Looper对象所持有。

现在Looper和MessageQueue对象已经创建完成了。我们再回到prepareMainLooper方法中。在通过prepare方法创建完Looper对象和MessageQueue对象后,系统又调用了Looper的myLooper方法,而myLooper方法返回的其实是刚才创建的该线程所独有的Looper对象,这里即是主线程所对应的Looper对象。系统将这个对象赋值给了一个全局变量sMainLooper,方便之后使用getMainLooper方法来直接拿取主线程的Looper对象。myLooper方法的代码如下:

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

至此准备工作都已经完成了,系统只需要再通过Looper.loop方法让消息系统运行起来即可,loop方法的源码如下:

    public static void loop() {
        final Looper me = myLooper();   //获取looper对象
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;  //获取MessageQueue对象
        ...
        for (;;) {
            Message msg = queue.next(); // 从MessageQueue对象中获取一个消息
            if (msg == null) {
                return;
            }
           ...
            try {
                msg.target.dispatchMessage(msg);//将这个消息分发给相应的Handler进行处理
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            msg.recycleUnchecked();
        }
    }

loop方法的代码较多,为了便于理解,省去了部分代码。首先通过myLooper方法获取当前线程对应的Looper对象,然后又通过me.mQueue拿取了looper对象内部的MessageQueue。之后开启了一个无限循环,在循环中,首先通过queue.next()取出MessageQueue中存储的一个消息,如果这个消息不为null,则通过msg.target.dispatchMessage(msg)分发给相应的Handler进行处理。msg.target其实就是发出该消息的Handler对象。Handler在发出一个消息时会将自身存储在Message内部的target变量中,之后在分析Handler时会讲到。我们先来看一下dispatchMessage方法的源码:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);    //通过handleCallback来执行Message对象内部的Runnable
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg); //将Message交给handleMessage方法进行处理
        }
    }

首先会检查msg的callback对象是否为null,这个callback是一个Runnable类型的对象,我们知道Handler可以发出两种类型的消息,一种是通过sendMessage等方法直接发送一个Message消息对象,另一种是通过则会通过post方法发送一个Runnable对象。如果发送的是一个Runnable对象,Handler在内部也会将这个Runnable对象封装成一个Message对象,并将原来的Runnable对象赋值给Message的callback变量。如果msg.callback不为null,说明该消息原本是通过Handler的post方法发出的一个Runnable,那么会通过handleCallback方法直接执行这个Runnable。如果msg.callback对象为null,那么就将这个msg交给Handler的handleMessage方法进行处理。我们在创建Handler对象时通常会重写handleMessage方法来实现我们想要的逻辑。

由于loop方法内部其实是一个无限循环,因此Looper对象会不断的从MessageQueue对象中拿取消息并分发给对应的Handler进行处理。需要注意的是,如果我们在子线程中调用了Looper.loop方法,那么Looper中的无限循环会导致子线程阻塞,因此当我们在子线程中使用了Looper后,应该在适当的时机调用looper对象的quit或quitSafely方法来退出这个Looper。

至此整个主线程的消息系统已经创建完成并且开始工作了,接下来我们看一下Handler是如何将一个消息提交给相应的MessageQueue的。

消息的发送过程

Handler对象负责消息的发送和处理。我们先来看一下Handler对象的构造方法:

    public Handler(Callback callback, boolean async) {
        ...
        mLooper = Looper.myLooper();//与Looper进行了绑定
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;//与MessageQueue绑定
        mCallback = callback;  
        mAsynchronous = async;
    }

Handler对象有许多重载的构造方法,但这些构造方法最终都是调用的Hanler(Callback callback, boolean async)这个构造方法。在这个构造方法中,首先通过Looper.myLooper获取到了一个Looper对象并赋值给了自己的一个成员变量,前面我们说过,myLooper对象返回的是当前线程所独有的Looper对象,这样一来Handler,Looper和线程之间就一一对应起来了。因此,无论handler在哪个线程发出消息,这个消息最终都会在handler初始化时所绑定的Looper所对应的线程中进行处理。

前面我们说过,Handler可以发出两种类型的消息,一种是通过sendMessage方法发送一个Message对象,另一种是通过post方法发送一个Runnable。我们先来看一下sendMessage方法和post方法的源码:

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

可以看到无论是post方法还是sendMessage方法最终都是调用的sendMessageDelayed方法,不同的是在post方法中先调用了getPostMessage方法来对Runnable对象进行了一些处理,我们来看一下getPostMessage方法:

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

正如我们前面所说的,Handler将通过post方法提交的Runnable封装在了一个Message对象内的callback变量里。接下来我们看一下sendMessageDelayed方法:

    public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    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);
    }

可见在sendMessageDelayed方法中又调用了sendMessageAtTime方法。而在sendMessageAtTime方法中,先拿取了初始化时绑定的MessageQueue对象,然后将这个MessageQueue和Message对象一起传给了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将自身赋值给了Message的target变量,前面在讲loop方法的时候也说过,Message最终会通过这个target变量来获取对应的Handler,因此,Message最终会被发出该消息的Handler所处理。之后又调用了MessageQueue的enqueueMessage方法,最终将这个Message提交给了对应的MessageQueue对象。MessageQueue实际上是一个单链表型的数据结构,链表中的前一个元素都会持有下一个元素的引用,而MessageQueue只需要持有第一个元素的引用即可。看一下MessageQueue的enqueueMessage方法:

    boolean enqueueMessage(Message msg, long when) {
            ...
            msg.when = when;
            Message p = mMessages;//当前链表中的首个元素
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {//如果链表中没有元素或者要插入的message的执行时间早于队列中的首个元素
                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; //
                prev.next = msg;
            }
            ...
        }
        return true;
    }

当插入一个新的Message时,MessageQueue首先会判断当前链表中是否存在元素,如果集合中的首个元素为null,那么就说明这个集合现在也是空的。如果集合中不存在元素,或新插入的Message不需要延时执行,或者要插入的Message的执行时间要早于集合中的首个元素的话,那么直接将链表中的首个元素设置为新插入的Message的下个元素,并将新插入的元素设置为队列中的首个元素。如果不满足上述条件的话,那么会从头开始遍历集合,根据Message的执行时间来将Message插入到集合中的相应位置。可见MessageQueue虽然名字中带有Queue,但并不是一个标准的队列,因为队列只允许在表的后端插入元素。

至此,一个Message就成功被Handler提交到了Message中了。

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

推荐阅读更多精彩内容