文章目录

    • 前言
    • 概述
    • Message
    • MessageQueue
    • ThreadLocal
    • Looper
    • Handler
    • 总结
    • 感谢

前言

我们都知道,GUI 应用程序是消息(事件)驱动的,Android 应用程序也是如此。消息的传递要依赖于应用框架提供的消息机制。Android 系统提供了两种消息机制:

组件间消息:Intent 机制

线程间消息:Message 机制

通常说 Android 的消息机制,都是单指 Message 机制,我也不能免俗,这篇文章,我们要探讨的依旧是这个老生常谈的 Message 机制。

概述

在开发中,大家常打交道的是 Handler,我们常用 Handler 来更新 UI,实际上更确切的讲,Handler 是用来切换线程的。

比如我们常在子线程中进行 请求网络、存取文件、操作数据库等 耗时操作,操作完成后通过 Handler 切换到主线程对 UI 进行一些改变;当然也会出现在子线程中再创建子线程,Handler 用于子线程和子线程的子线程间的切换也是可以的。

所以我们说 Android 的消息机制,基本可以认为就是 Handler 的工作机制。 Handler 最重要的工作就是收发 Message ;Handler 的工作需要 MessageQueueLooper 的支持;Looper 是运行在创建 Handler 的线程中的,而以线程为作用域的数据存取需要用到 ThreadLocal 这个数据存储类。

这几个标红的类就是我们讨论 Android 消息机制要关注的,我们来看一下他们的 UML 类图。

图 1    UML 类图



关于消息机制的工作流程,也贴一张图,我们有个大体印象。

图 2    消息机制的工作流程

消息机制的工作流程分三步进行:

1、Handler 调用 sendMessageMessage 入队到 MessageQueue

2、Looper 类的 loop() 方法中无限循环,从 MessageQueue 中取出 Message ,再交回给 Handler

3、Handler 调用 dispatchMessage 分发处理消息


说明一下,本篇文章不会对源码进行详细分析,主要是梳理一下工作流程及实现方式,另外会关注一些官方建议用法。

结合源码的详细分析的各种资源,很多了,书籍的话推荐任玉刚的《Android 开发艺术探索》,博客的话可以看张拭心的 Android 进阶14:源码解读 Android 消息机制( Message MessageQueue Handler Looper)。

Message

“消息机制” 要传递的消息,就是这个 Message 了。

在其类描述中,官方有这么一句话:

While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.

简单翻译一下:当需要获取一个 Message 对象时,不建议通过构造方法进行实例化,尽管构造方法是 public 的,建议通过 Message.obtain() 或者 Handler.obtainMessage() 这两种方式,他们会从消息池中拉取一个消息复用。

也就是说,消息与消息池,就相当于线程与线程池,从消息池中复用已经处理完毕的消息,要比直接通过构造方法 new 新的消息更节省资源。当消息池为空时,系统会自动 new 一个新的消息返回给调用者。另外,消息池的最大容量是 50。

Message 类中有一个属性 int FLAG_IN_USE

    /** If set message is in use.     * This flag is set when the message is enqueued and remains set while it     * is delivered and afterwards when it is recycled.  The flag is only cleared     * when a new message is created or obtained since that is the only time that     * applications are allowed to modify the contents of the message.     *     * It is an error to attempt to enqueue or recycle a message that is already in use.     */    /*package*/ static final int FLAG_IN_USE = 1 << 0;    

这个属性用来标示消息是否在用,当消息入队或者被回收FLAG_IN_USE 会被置为 1,表示消息在用,此时如果调用 mHandler.sendMessage(msg) 将消息再次入队,会报异常 This message is already in use

Message 类中有一个属性 Message next,这个属性与 MessageQueue 息息相关,我们下面分析。

MessageQueue

MessageQueue 管理着一个 Message 的列表,Handler 向其中添加消息,Looper 从其中取消息。

MessageQueue 虽然叫做消息队列,但其内部存储结构却不是队列,而是单链表的数据结构,如下图所示。

图 3    消息队列的单链表结构

MessageQueue 中的属性 Message mMessages 指向第一个消息节点 m1 ,每个消息节点通过上述的 Message next 属性,依次持有其后一个消息节点的引用。这就是单链表实现的消息队列。

MessageQueue 有几个方法我们要关注一下。

1、enqueueMessage() 入队,由 Handler 调用,会将新消息添加到队尾。

2、next() 出队,由 Looper 调用,会从队首取出要立即处理的消息。当队列中没有消息时,next() 方法会阻塞。

3、quit(boolean safe) 队列退出,由 Looper 调用。从入参可明显看出,队列的退出分为非安全退出和安全退出,分别调用 removeAllMessagesLocked()removeAllFutureMessagesLocked() 两个方法。

当队列已退出,还通过 Handler 向队列中添加消息的话,会报异常 sending message to a Handler on a dead thread

4、removeAllMessagesLocked() 非安全退出队列,将消息队列中所有消息全部回收,不再处理。

5、removeAllFutureMessagesLocked() 安全退出队列,遍历消息队列,当找到第一个延迟消息时,将其与其后的所有消息回收。

什么是延迟消息?比如说通过 mHandler.postDelayed(Runnable r, long delayMillis) 等方式向消息队列中添加的消息,设置了消息的延迟处理时间。

可以发现,我们一般不会直接操作 MessageQueue,就连 MessageQueue 的实例化,都是在 Looper 的构造中完成的。

在分析 Looper 之前,我们要先了解下 ThreadLocal ,这有助于我们更好的理解 Looper。

ThreadLocal

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

这是《Android 开发艺术探索》中的解释。也就是说,我们通过 ThreadLocal 可将线程的数据存储起来,且只有相应线程可以访问获取。

下面我们来看一下 Looper。

Looper

Looper 负责消息调度,也就是负责从 MessageQueue 中取出消息,交给 Handler 进行处理。

一个线程只能有一个 Looper,但默认没有 Looper,要通过 Looper.prepare() 创建一个 Looper ,之后再调用 Looper.loop() 开始循环获取 Message 处理。

并且,Handler 的工作需要有 Looper,没有 Looper 会报错 Can't create handler inside thread that has not called Looper.prepare()

在 Looper 的类描述中,官方提供了一段代码示例:

    class LooperThread extends Thread {        public Handler mHandler;          public void run() {            Looper.prepare();              mHandler = new Handler() {                public void handleMessage(Message msg) {                    // process incoming messages here                }            };              Looper.loop();        }    }    

那么可能会有这样的疑问:

1、我们创建的子线程,大多数情况下并没有调用 Looper.prepare()Looper.loop() 啊?

这是因为我们的这些子线程,并没有使用 Handler 。上文提到 “Handler 的工作需要有 Looper”,不用 Handler,自然就不需要 Looper 了。

2、UI 主线程中,可以使用 Handler,但我们并没有调用 Looper.prepare()Looper.loop() 啊?

这是因为在程序的 main() 函数中,系统已经帮我们调用了 Looper.prepare()Looper.loop() 。main() 函数位于 ActivityThread 类中。

Looper 中还有两个重要的方法 quit()quitSafely() ,其内部实现极其简单

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

就是直接调用 MessageQueue 的退出方法,将消息队列给退出了。那 Looper 的无限循环方法 loop() 怎么退出呢?

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

很简单,当从消息队列中拿不到消息时,loop() 方法就会退出。

那从消息队列拿不到消息,一定是消息队列退出了,没有可能是因为消息队列中的消息处理完了吗?

没有可能,因为如果是因为消息队列中的消息处理完了,queue.next() 会阻塞,是拿不到 null 消息的。

所以,如果子线程中用了 Looper,一定要调用 Looper.quit() 方法进行退出,否则 queue.next() 会一直阻塞,消息队列和 Looper 都无法回收,会造成内存泄漏。

下面我们来看下 Handler。

Handler

我们知道,Handler 发送消息可以通过 sendXxx() 系列方法和 postXxx() 系列方法。

postXxx() 系列方法,内部还是将入参的 Runnable 包装成 Message ,通过 sendXxx() 系列方法,最终调用 MessageQueue 的 enqueueMessage() 将消息入队。

至于 Handler 消息处理的回调方法,有如下三种重写方式:

第一种:

        Handler mHandler = new Handler();        mHandler.post(new Runnable() {            @Override            public void run() {            }        });

第二种:

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

第三种:

        Handler mHandler = new Handler(){            @Override            public void handleMessage(Message msg) {                super.handleMessage(msg);            }        };

我们上文说过,Looper 的 loop() 方法拿到消息后,会交给 handler 进行处理:

msg.target.dispatchMessage(msg)

我们看下 dispatchMessage(msg) 源码:

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

这段代码中的 第 4 行 ,调用的就是上面的回调方法重写的第一种方式;

这段代码中的 第 7 行 ,调用的就是上面的回调方法重写的第二种方式;

这段代码中的 第 11 行 ,调用的就是上面的回调方法重写的第三种方式。

好了,Handler 做的工作就这么多,可以发现,它就干两件事:调用消息队列将消息入队,调用回调方法处理消息。

总结

经过上文对各个类的梳理,我们再来看看下面这张图,会发现 Android 消息机制的工作流程并不复杂。

如果各位还想对源码做更深入的了解,参考后文的感谢链接吧。



感谢

  • 《Android 开发艺术探索》 任玉刚
  • Android 进阶14:源码解读 Android 消息机制( Message MessageQueue Handler Looper)
  • Android Message机制及其应用

更多相关文章

  1. Android(安卓)Binder入门指南之Binder服务的消息循环
  2. Android异步处理三:Handler+Looper+MessageQueue深入详解
  3. Android异步消息处理机制Handler完全解析
  4. Android中UI主线程与子线程
  5. android -------- LiveDataBus的使用
  6. android使用HttpClient和URLConnection获取网页内容
  7. Android性能调优
  8. Android的Handler总结(1)
  9. Android四大组件之 服务Service

随机推荐

  1. Android(安卓)自动编译、打包生成apk文件
  2. Android中的Shape使用总结
  3. Android之查看外部依赖jar的源码_android
  4. android UI开发及常用控件
  5. Android(安卓)APK反编译详解(附图)
  6. 高级组件之自动完成文本框
  7. Android布局(相对布局)
  8. android makefile prebuild
  9. Android(安卓)缩放、移动、旋转View相关
  10. Android之查看外部依赖jar的源码_android