Android异步消息机制初探

Android中,当我们需要在子线程中进行网络请求等耗时操作后,如果需要更新UI时,通常会考虑使用Handler来处理,一种常用的写法如下:

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1){
                // update ui
            }
        }
    };

new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = Message.obtain();
                message.what = 1;
                mHandler.sendMessage(message);
            }
        });

除了上边我们用到的Handler类外,Android消息机制要正常工作还需要LooperMessageQueue两个类的配合,下来我们分别介绍三个类,以及它们之间的工作原理:

1、MessageQueue

顾名思义就是消息队列,但其实MessageQueue的底层是通过单链表来实现的,MessageQueue是用来中转Handler对象发送出的消息,主要包括enqueueMessagenext两个操作,分别用来插入一条消息和取出一条消息,因为链表在插入和删除操作上有较高的效率,这可能就是使用该数据结构的原因吧。MessageQueue只负责保存消息,并不会处理消息,MessageQueue对象如何得到呢?继续往下看。

2、Looper

如果我们是在UI线程中使用Handler来发送异步消息,系统已经在当前UI线程通过Looper.prepareMainLooper()帮助我们创建好Looper对象,并调用Looper.loop()开启无限消息循环,不断从MessageQueue的实例中读取消息。如果在子线程中我们不手动创建Looper对象,则会抛出java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()的异常,所以在子线程中创建并使用Handler对象,就必须手动调用Looper.prepare()创建Looper对象,并通过Looper.loop()开启消息循环。
在子线程中可通过如下方式使用Android消息机制:

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//创建Looper对象
                Handler handler = new Handler();
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        //do something
                    }
                });
                Looper.loop();//开启消息循环
            }
        });

这样子线程同样具有了异步消息循环处理的能力。
我们先通过源码分析一下Looper.prepare()方法

public static void prepare() {
        prepare(true);
    }

很简单,只有一行代码,继续进入到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));
    }

重点看一下new Looper()方法

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

可以看到在创建Looper对象时也创建了MessageQueue对象,此时Looper对象持有了MessageQueue对象的引用。并与当前线程绑定,保证一个线程只会有一个Looper对象,同时一个Looper对象也只有一个MessageQueue对象。

3、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 that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

通过mLooper = Looper.myLooper(),我们Handler对象拿到了对应Looper对象的引用,产生了关联。
可以看到如果Looper等于null的话会抛出异常,这也验证了上边第二点中的说法,创建Handler对象时必须有Looper对象存在。
再看 mQueue = mLooper.mQueue这一行,其实就是将Handler的实例与我们Looper实例中创建的MessageQueue对象关联起来。此时MessageQueue对象已经和Looper对象以及Handler对象关联了起来,并且他们在同一线程中。

4、工作原理

结合文章开头的例子来分析 Handler 具体的工作原理。

通过Handler对象发送消息时可用的方法非常多,例如常用的sendMessage系列方法,post系列方法等,通过源码可以知道,这两系列方法都最终都是调用下边的方法来实现的:

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

继续看下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);
    }

其核心的的就是最后一行,跟进去

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

首先看第一行msg.target = this,将Handler对象赋值给的Message对象的target属性,此时Message对象持有了Handler对象的引用。
最后一行通过调用MessageQueue 对象的enqueueMessage方法来保存发送的消息。

上边Looper类中还有一个Looper.loop()方法没分析,调用该方法后,则会一直检测MessageQueue对象中是否有数据,有的话则取出,否则阻塞,此时我们来看一下源码:

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

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);
            ........
            省略
            ........
        }
    }

只看核心的代码,首先是一个无限的for循环,循环中调用MessageQueue 的next方法不断的取出Message对象,没有消息则进入阻塞状态。否则执行msg.target.dispatchMessage(msg),这里的msg.target就是上边enqueueMessage方法中的msg.target,也就是Handler对象的引用,所以此时消息就可以调用Handler的dispatchMessage()方法进行消息处理。此时Handler对象发出的消息在MessageQueue对象中通过 Looper 完成了中转,由于Looper是在主线程创建并开启消息循环的,所以dispatchMessage()在主线程被调用:

接下来看一下dispatchMessage方法:

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

首先看第一行if判断,什么时候msg.callback != null成立呢,其实就是当我们通过如下形式发送消息时

handler.post(new Runnable() {
                    @Override
                    public void run() {
                        // update ui
                    }
                });

继续查看post方法

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

进入getPostMessage方法中

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

记住m.callback = r这一行,此时再看dispatchMessage中第一个if条件的执行代码:

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

方法中执行的正是callback 中的run方法

if (mCallback != null)什么时候成立呢?当我们通过如下方式使用Handler时成立

private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            return false;
        }
    });

从而下边的代码得到执行

if (mCallback.handleMessage(msg)) {
                    return;
                }

即就是我们上边new Handler.Callback()中的handleMessage方法

最后如果我们采用文章最开始的代码执行handler操作则会执行dispatchMessage中最后一行handleMessage(msg)方法:

public void handleMessage(Message msg) {}

可以看到是一个空的方法,因为需要我们自己去实现哦!同时 Handler 也是在主线程创建的,所以 handleMessage()方法最终在主线程执行,这样就完成了从子线程到主线程的切换。

最后说一下,创建Message对象,可以通过new方法 ,也可以使用Message.obtain()方法,但建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。

到这里你有没有发现LooperMessageQueueHandler之间的关系呢?

  1. 在Looper中创建了MessageQuene,Looper循环从MessageQuene取出消息,由于消息持有Handler的引用,最终会调用Handler的handleMessage()方法或其它方法,Looper会被保存在ThreadLocal中,一个线程对应唯一Looper。
  2. Handler发送消息时,会从ThreadLocal中取出对应线程的Looper,再从中得到对应的MessageQuene,发送的消息会持有Handler的引用,然后将消息保存到MessageQuene。
  3. 关于ThreadLocal在下一篇有讲到

到此Android消息机制的基本原理已经分析完了,简单的概括一下:通过Handler对象的相关方法将Message对象发送出去进而插入到MessageQueue对象中,然后Looper对象调用loop方法,进一步会执行MessageQueue对象的next方法取出消息,交给Handler对象的dispatchMessage方法来处理。这就是Android消息机制一个大致的流程。

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

推荐阅读更多精彩内容