Handler MessageQueue Looper消息循环原理分析

 

Handler概述

Handler在Android开发中非常重要,最常见的使用场景就是在子线程需要更新UI,用Handler来投递消息到主线程执行UI更新操作。因为Android系统的View是非线程安全的,所以需要在主线程更新UI。总的来说Handler就是用来做线程间通信,在不同线程之间传递消息。注:这篇文章所讲到的Handler是在主线程创建的,主线程在开始的时候已经创建了默认的消息循环。后面的文章会讲如何创建自己的消息循环。

消息循环主体结图例分析

Handler Looper原理图

从图中可以看出,四种颜色分别代表了四个对象,并且大致描述了几个对象之间的关系,以及消息的流转过程,首先Handler通过sendMessage将消息投递给MessageQueue,Looper通过消息循环(loop)不断的从MessageQueue中取出消息,然后消息被Handler的dispatchMessage分发到handleMessage方法消费掉。

消息循环中涉及的重要对象

Handler

通过Handler的sendMessage等方法来投递消息到MessageQueue,通过handleMessage来消费Message。Handler必须要有一个已经prepare好的Looper对象,也就是说必须调用了prepare方法(也包括prepareMainLooper方法),究其根本是初始化一个消息队列,这一过程将在下文中详细分析。

Looper

Looper负责从MessageQueue中取出消息,然后通过执行message.target.dispatchMessage()消费掉这个消息,这里的target就是Handler。

MessageQueue

消息队列,管理Handler投递过来的消息。

Message

用来承载数据的消息,最终被Handler消费掉。

UML类图分析

Handler class diagram

通过上面的类图可以清晰的了解各个类之间的关系。然后再来分析一下源码。

Handler的创建

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
 
        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;
}
  • 在上面这段代码中,首先是检查是否存在潜在的内存泄漏,如果该类是匿名内部类,或者是成员类且没有static修饰符时那么打印一个内存泄漏风险警告。这是由于这种类型的class持有外部类的this引用,可能导致外部类无法释放。
  • 接下来就是对成员变量mLooper赋值,在文章开头就提到过,这篇文章中提到的handler对象时在主线程(UI线程)中创建,而Android主线已经有一个消息队列了,所以直接将mLooper.mQueue赋给Handler的mQueue。

那么主线程中的消息队列是怎么创建的呢?

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void prepare() {
        prepare(true);
}
public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
}
 
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        将looper对象装入ThreadLocal中,Handler就是从它里面取出looper对象的
        sThreadLocal.set(new Looper(quitAllowed));
}
 
private Looper(boolean quitAllowed) {
        //创建消息队列
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

看上面的关键代码,UI线程在创建的时候,会调用prepareMainLooper()这个方法,创建一个不退出的消息队列。所以prepareMainLooper这个方法自己永远也不要调用,它是系统调用的,如果我们需要用自己的消息队列呢?那么就应该调用prepare()方法。

消息怎么被消费的呢?

整个消息循环系统中的几个重要部件的创建都已经明白了,那么消息时怎么循环起来的,又是如何消费的呢?来看看下面是loop源码的一部分关键代码。代码非常简单易懂,就是从消息队列中取出消息,然后通过msg.target.dispatchMessage(msg)将消息投递到Handler。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            msg.target.dispatchMessage(msg);
            msg.recycleUnchecked();
        }
    }

消息传递的终点

 
1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
}

当消息循环中取出的消息被再次传递给Handler的时候,这个消息就走到了生命的尽头(并不代表对象销毁,有一个消息池来回收消息),从dispatchMessage方法可以看出,消息最终的归宿有三个,一是消息自身的callback接口,二是handler的callback接口,最后是handleMessage接口。

总结

Handler获取当前线程中的looper对象,looper用来从存有Message的Message Queue里取出message,再由Handler进行message的分发和处理。

copyright@黑月神话,转载请注明出处:vjson.com

[转]Handler MessageQueue Looper消息循环原理分析的更多相关文章

  1. Android Handler MessageQueue Looper 消息机制原理

    提到Android里的消息机制,便会提到Message.Handler.Looper.MessageQueue这四个类,我先简单介绍以下这4个类 之间的爱恨情仇. Message 消息的封装类,里边存 ...

  2. Eureka 系列(05)消息广播(上):消息广播原理分析

    Eureka 系列(05)消息广播(上):消息广播原理分析 [TOC] 0. Spring Cloud 系列目录 - Eureka 篇 首先回顾一下客户端服务发现的流程,在上一篇 Eureka 系列( ...

  3. Android应用程序线程消息循环模型分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6905587 我们知道,Android应用程序是 ...

  4. 从Handler+Message+Looper源代码带你分析Android系统的消息处理机制

    PS一句:不得不说CSDN同步做的非常烂.还得我花了近1个小时恢复这篇博客. 引言 [转载请注明出处:http://blog.csdn.net/feiduclear_up CSDN 废墟的树] 作为A ...

  5. 异步消息处理(Message, Handler, MessageQueue, Looper)

    AsyncTask 适用于单线程任务处理,多任务处理还是 Message/Handler 处理方便一些 主要使用方式: 1,创建子类继承自 Handler 类,覆盖 handleMessage(Mes ...

  6. Android的消息循环机制 Looper Handler类分析

    Android的消息循环机制 Looper Handler类分析 Looper类说明   Looper 类用来为一个线程跑一个消息循环. 线程在默认情况下是没有消息循环与之关联的,Thread类在ru ...

  7. Android Handler 机制 - Looper,Message,MessageQueue

    Android Studio 2.3 API 25 从源码角度分析Handler机制.有利于使用Handler和分析Handler的相关问题. Handler 简介 一个Handler允许发送和处理M ...

  8. RocketMQ延迟消息的代码实战及原理分析

    RocketMQ简介 RocketMQ是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的.高可靠.万亿级容量.灵活可伸缩的消息发布与订阅服务. 它前身是MetaQ,是阿里基于Kafka ...

  9. Android中线程间通信原理分析:Looper,MessageQueue,Handler

    自问自答的两个问题 在我们去讨论Handler,Looper,MessageQueue的关系之前,我们需要先问两个问题: 1.这一套东西搞出来是为了解决什么问题呢? 2.如果让我们来解决这个问题该怎么 ...

随机推荐

  1. 推荐 10 个超棒的 CSS3 代码生成工具

    新的在线工具和 WebApp 帮助开发者快速地创建网站而不用写代码.前端开发已经在框架和代码库方面有了很大的进展. 但是许多开发者已经忘记了代码生成器在构建网站时的价值.下面的资源是完全免费的 Web ...

  2. MySql循环插入数据(定义了存储过程)

    MySQL一窍不通啊,今天工作上需要用到,请教了别人,做以备忘 DROP PROCEDURE test_insert ; DELIMITER ;; CREATE PROCEDURE test_inse ...

  3. thinkphp-3

    有两种创建项目的方式: 一是用多个单入口文件, 一个入口文件对应着一个项目, 如前台/后台/会员中心等 二是用一个单入口, 创建项目分组 对于有多个入口文件的 情况, 配置文件的共享问题? 不管是前台 ...

  4. linux命令--dig

    dig,和nslookup作用有些类似,都是DNS查询工具,但是却比nslookup强大 dig,其实是一个缩写,即Domain Information Groper. [我想用google-DNS来 ...

  5. Maven初级学习(三)常用命令

    依赖关系查看 mvn dependency:list #列表形式展示依赖 mvn dependency:tree #层级关系展示依赖 mvn dependency:analyze #依赖分析 声明周期 ...

  6. web网页颜色色谱

    snow 255 250 250 #fffafa ghostwhite 248 248 255 #f8f8ff whitesmoke 245 245 245 #f5f5f5 gainsboro 220 ...

  7. PHP基础之 数组(二)

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...

  8. HDOJ 4750 Count The Pairs

    按边长从小到大排序...再逐个加入(就像MST一样)最先联通的点之间最长路径中的最小值就是新加入的边的长.... Count The Pairs Time Limit: 20000/10000 MS ...

  9. eclipse emacs

    eclipse emacs 插件 http://www.mulgasoft.com/emacsplus eclipse字体设置: 一.把字体设置为Courier New  操作步骤:打开Elcipse ...

  10. Ping CAP CTO、Codis作者谈redis分布式解决方案和分布式KV存储

    此文根据[QCON高可用架构群]分享内容,由群内[编辑组]志愿整理,转发请注明出处. 苏东旭,Ping CAP CTO,Codis作者 开源项目Codis的co-author黄东旭,之前在豌豆荚从事i ...