转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/17361775

前言

上周对Android中的事件派发机制进行了分析,这次博主要对消息队列和Looper的源码进行简单的分析。大家耐心看下去,其实消息队列的逻辑比事件派发机制简单多了,所以大家肯定会很容易看懂的。

概念

1. 什么是消息队列

消息队列在android中对应MessageQueue这个类,顾名思义,消息队列中存放了大量的消息(Message)

2.什么是消息

消息(Message)代表一个行为(what)或者一串动作(Runnable),有两处会用到Message:Handler和Messenger

3.什么是Handler和Messenger

Handler大家都知道,主要用来在线程中发消息通知ui线程更新ui。Messenger可以翻译为信使,可以实现进程间通信(IPC),Messenger采用一个单线程来处理所有的消息,而且进程间的通信都是通过发消息来完成的,感觉不能像AIDL那样直接调用对方的接口方法(具体有待考证),这是其和AIDL的主要区别,也就是说Messenger无法处理多线程,所有的调用都是在一个线程中串行执行的。Messenger的典型代码是这样的:new Messenger(service).send(msg),它的本质还是调用了Handler的sendMessage方法

4.什么是Looper

Looper是循环的意思,它负责从消息队列中循环的取出消息然后把消息交给目标处理

5.线程有没有Looper有什么区别?

线程如果没有Looper,就没有消息队列,就无法处理消息,线程内部就无法使用Handler。这就是为什么在子线程内部创建Handler会报错:"Can't create handler inside thread that has not called Looper.prepare()",具体原因下面再分析。

6.如何让线程有Looper从而正常使用Handler?

在线程的run方法中加入如下两句:

Looper.prepare();

Looper.loop();

这一切不用我们来做,有现成的,HandlerThread就是带有Looper的线程。

想用线程的Looper来创建Handler,很简单,Handler handler = new Handler(thread.getLooper()),有了上面这几步,你就可以在子线程中创建Handler了,好吧,其实android早就为我们想到这一点了,也不用自己写,IntentService把我们该做的都做了,我们只要用就好了,具体怎么用后面再说。

消息队列和Looper的工作机制

一个Handler会有一个Looper,一个Looper会有一个消息队列,Looper的作用就是循环的遍历消息队列,如果有新消息,就把新消息交给它的目标处理。每当我们用Handler来发送消息,消息就会被放入消息队列中,然后Looper就会取出消息发送给它的目标target。一般情况,一个消息的target是发送这个消息的Handler,这么一来,Looper就会把消息交给Handler处理,这个时候Handler的dispatchMessage方法就会被调用,一般情况最终会调用Handler的handleMessage来处理消息,用handleMessage来处理消息是我们常用的方式。

源码分析

1. Handler发送消息的过程

  1. public final boolean sendMessage(Message msg)
  2. {
  3. return sendMessageDelayed(msg, 0);
  4. }
  5.  
  6. public final boolean sendMessageDelayed(Message msg, long delayMillis)
  7. {
  8. if (delayMillis < 0) {
  9. delayMillis = 0;
  10. }
  11. return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
  12. }
  13.  
  14. public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  15. MessageQueue queue = mQueue;
  16. if (queue == null) {
  17. RuntimeException e = new RuntimeException(
  18. this + " sendMessageAtTime() called with no mQueue");
  19. Log.w("Looper", e.getMessage(), e);
  20. return false;
  21. }
  22. return enqueueMessage(queue, msg, uptimeMillis);
  23. }
  24.  
  25. private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  26. msg.target = this;
  27. if (mAsynchronous) {
  28. msg.setAsynchronous(true);
  29. }
  30. //这里msg被加入消息队列queue
  31. return queue.enqueueMessage(msg, uptimeMillis);
  32. }

2.Looper的工作过程

  1. public static void loop() {
  2. final Looper me = myLooper();
  3. if (me == null) {
  4. throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  5. }
  6. //从Looper中取出消息队列
  7. final MessageQueue queue = me.mQueue;
  8.  
  9. // Make sure the identity of this thread is that of the local process,
  10. // and keep track of what that identity token actually is.
  11. Binder.clearCallingIdentity();
  12. final long ident = Binder.clearCallingIdentity();
  13.  
  14. //死循环,循环的取消息,没有新消息就会阻塞
  15. for (;;) {
  16. Message msg = queue.next(); // might block 这里会被阻塞,如果没有新消息
  17. if (msg == null) {
  18. // No message indicates that the message queue is quitting.
  19. return;
  20. }
  21.  
  22. // This must be in a local variable, in case a UI event sets the logger
  23. Printer logging = me.mLogging;
  24. if (logging != null) {
  25. logging.println(">>>>> Dispatching to " + msg.target + " " +
  26. msg.callback + ": " + msg.what);
  27. }
  28.  
  29. //将消息交给target处理,这个target就是Handler类型
  30. msg.target.dispatchMessage(msg);
  31.  
  32. if (logging != null) {
  33. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
  34. }
  35.  
  36. // Make sure that during the course of dispatching the
  37. // identity of the thread wasn't corrupted.
  38. final long newIdent = Binder.clearCallingIdentity();
  39. if (ident != newIdent) {
  40. Log.wtf(TAG, "Thread identity changed from 0x"
  41. + Long.toHexString(ident) + " to 0x"
  42. + Long.toHexString(newIdent) + " while dispatching to "
  43. + msg.target.getClass().getName() + " "
  44. + msg.callback + " what=" + msg.what);
  45. }
  46.  
  47. msg.recycle();
  48. }
  49. }

3.Handler如何处理消息

  1. /**
  2. * Subclasses must implement this to receive messages.
  3. */
  4. public void handleMessage(Message msg) {
  5. }
  6.  
  7. /**
  8. * Handle system messages here.
  9. */
  10. public void dispatchMessage(Message msg) {
  11. if (msg.callback != null) {
  12. //这个方法很简单,直接调用msg.callback.run();
  13. handleCallback(msg);
  14. } else {
  15. //如果我们设置了callback会由callback来处理消息
  16. if (mCallback != null) {
  17. if (mCallback.handleMessage(msg)) {
  18. return;
  19. }
  20. }
  21. //否则消息就由这里来处理,这是我们最常用的处理方式
  22. handleMessage(msg);
  23. }
  24. }

我们再看看msg.callback和mCallback是啥东西

/*package*/ Runnable callback;

现在已经很明确了,msg.callback是个Runnable,什么时候会设置这个callback:handler.post(runnable),相信大家都常用这个方法吧

  1. /**
  2. * Callback interface you can use when instantiating a Handler to avoid
  3. * having to implement your own subclass of Handler.
  4. *
  5. * @param msg A {@link android.os.Message Message} object
  6. * @return True if no further handling is desired
  7. */
  8. public interface Callback {
  9. public boolean handleMessage(Message msg);
  10. }
  11.  
  12. final Callback mCallback;

而mCallback是个接口,可以这样来设置 Handler handler = new Handler(callback),这个callback的意义是什么呢,代码里面的注释已经说了,可以让你不用创建Handler的子类但是还能照样处理消息,恐怕大家常用的方式都是新new一个Handler然后override其handleMessage方法来处理消息吧,从现在开始,我们知道,不创建Handler的子类也可以处理消息。多说一句,为什么创建Handler的子类不好?这是因为,类也是占空间的,一个应用class太多,其占用空间会变大,也就是应用会更耗内存。

HandlerThread简介

  1. @Override
  2. public void run() {
  3. mTid = Process.myTid();
  4. Looper.prepare();
  5. synchronized (this) {
  6. mLooper = Looper.myLooper();
  7. notifyAll();
  8. }
  9. Process.setThreadPriority(mPriority);
  10. onLooperPrepared();
  11. Looper.loop();
  12. mTid = -1;
  13. }

HandlerThread继承自Thread,其在run方法内部为自己创建了一个Looper,使用上HandlerThread和普通的Thread不一样,无法执行常见的后台操作,只能用来处理新消息,这是因为Looper.loop()是死循环,你的code根本执行不了,不过貌似你可以把你的code放在super.run()之前执行,但是这好像不是主流玩法,所以不建议这么做。

IntentService简介

  1. public void onCreate() {
  2. // TODO: It would be nice to have an option to hold a partial wakelock
  3. // during processing, and to have a static startService(Context, Intent)
  4. // method that would launch the service & hand off a wakelock.
  5.  
  6. super.onCreate();
  7. HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
  8. thread.start();
  9.  
  10. mServiceLooper = thread.getLooper();
  11. mServiceHandler = new ServiceHandler(mServiceLooper);
  12. }

IntentService继承自Service,它是一个抽象类,其被创建的时候就new了一个HandlerThread和ServiceHandler,有了它,就可以利用IntentService做一些优先级较高的task,IntentService不会被系统轻易杀掉。使用IntentService也是很简单,首先startService(intent),然后IntentService会把你的intent封装成Message然后通过ServiceHandler进行发送,接着ServiceHandler会调用onHandleIntent(Intent intent)来处理这个Message,onHandleIntent(Intent intent)中的intent就是你startService(intent)中的intent,ok,现在你需要做的是从IntentService派生一个子类并重写onHandleIntent方法,然后你只要针对不同的intent做不同的事情即可,事情完成后IntentService会自动停止。所以,IntentService是除了Thread和AsyncTask外又一执行耗时操作的方式,而且其不容易被系统干掉,建议关键操作采用IntentService。

在子线程创建Handler为什么会报错?

  1. public Handler(Callback callback, boolean async) {
  2. if (FIND_POTENTIAL_LEAKS) {
  3. final Class<? extends Handler> klass = getClass();
  4. if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
  5. (klass.getModifiers() & Modifier.STATIC) == 0) {
  6. Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
  7. klass.getCanonicalName());
  8. }
  9. }
  10. //获取当前线程的Looper
  11. mLooper = Looper.myLooper();
  12. //报错的根本原因是:当前线程没有Looper
  13. if (mLooper == null) {
  14. throw new RuntimeException(
  15. "Can't create handler inside thread that has not called Looper.prepare()");
  16. }
  17. mQueue = mLooper.mQueue;
  18. mCallback = callback;
  19. mAsynchronous = async;
  20. }

如何避免这种错误:在ui线程使用Handler或者给子线程加上Looper。

Android源码分析-消息队列和Looper的更多相关文章

  1. [源码分析] 消息队列 Kombu 之 启动过程

    [源码分析] 消息队列 Kombu 之 启动过程 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Kombu 是 ...

  2. [源码分析] 消息队列 Kombu 之 Consumer

    [源码分析] 消息队列 Kombu 之 Consumer 目录 [源码分析] 消息队列 Kombu 之 Consumer 0x00 摘要 0x01 综述功能 0x02 示例代码 0x03 定义 3.1 ...

  3. [源码分析] 消息队列 Kombu 之 Producer

    [源码分析] 消息队列 Kombu 之 Producer 目录 [源码分析] 消息队列 Kombu 之 Producer 0x00 摘要 0x01 示例代码 0x02 来由 0x03 建立 3.1 定 ...

  4. [源码分析] 消息队列 Kombu 之 Hub

    [源码分析] 消息队列 Kombu 之 Hub 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Kombu 中的 ...

  5. [源码分析] 消息队列 Kombu 之 mailbox

    [源码分析] 消息队列 Kombu 之 mailbox 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Komb ...

  6. 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 百篇博客分析OpenHarmony源码 | v33.02

    百篇博客系列篇.本篇为: v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁 ...

  7. v77.01 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 | 新的一年祝大家生龙活虎 虎虎生威

    百篇博客分析|本篇为:(消息封装篇) | 剖析LiteIpc进程通讯内容 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁 ...

  8. v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码

    百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...

  9. Android源码分析(六)-----蓝牙Bluetooth源码目录分析

    一 :Bluetooth 的设置应用 packages\apps\Settings\src\com\android\settings\bluetooth* 蓝牙设置应用及设置参数,蓝牙状态,蓝牙设备等 ...

随机推荐

  1. oracle----修改表中的数据

    1. 修改表中的数据:UPDATE语句: 语法: UPDTAE table_name SET column1 = value1,... [WHERE conditions] (2),无条件的更新(没有 ...

  2. No Pain No Game

    hdu4630:http://acm.hdu.edu.cn/showproblem.php?pid=4630 题意:给定一个排序,求区间最大GCD. 题解:离散树状数组.首先把查询按左端点从大到小排序 ...

  3. 这个帖子要收藏,以后用得着--python 实时获取子进程输出

    在论坛上找到方法了,http://bbs.csdn.net/topics/340234292 http://blog.csdn.net/junshao90/article/details/821575 ...

  4. 浏览器助手,请求拦截,后台模拟键鼠操作,页内嵌入JS

    http://www.cnblogs.com/ /// <summary>        /// 网页浏览器助手        /// 请求拦截,后台模拟键鼠操作,页内嵌入JS       ...

  5. 学习笔记-[Maven实战]-第一章:Maven简介

    Maven简介: Maven 可翻译为:知识的积累,也可以翻译为"专家"或"内行". Maven 是一个跨平台的项目管理工具,是Apache组织中一个很成功的开 ...

  6. CentOS5.5 下编译安装 LAMP

    大纲 1.安装gcc编译器 2.卸载rpm安装的http和mysql软件 3.编译安装php依赖包 4.安装apache软件 5.安装mysql软件 6.安装php软件 7.安装memcache ph ...

  7. 转:理解 Linux 的硬链接与软链接

    Linux 的文件与目录 现代操作系统为解决信息能独立于进程之外被长期存储引入了文件,文件作为进程 创建信息的逻辑单元可被多个进程并发使用.在 UNIX 系统中,操作系统为磁盘上的文本与图像.鼠标与键 ...

  8. BZOJ_1617_[Usaco2008_Mar]_River_Crossing_渡河问题_(动态规划)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1617 FJ要把\(n\)头奶牛用船运到河对岸.FJ自己在船上要用\(m\)时间,如果运送\(i ...

  9. 虚拟主机 (Virtual Host)

    虚拟主机 (Virtual Host) 是在同一台机器搭建属于不同域名或者基于不同 IP 的多个网站服务的技术. 可以为运行在同一物理机器上的各个网站指配不同的 IP 和端口, 也可让多个网站拥有不同 ...

  10. 【转】简单几步让App Store软件下载快如迅雷 -- 不错!!!

    原文网址:http://pad.zol.com.cn/237/2376160_all.html 下载速度慢的原因     1)国内用户从苹果软件商店下载软件速度很慢这是大家都知道的事实,究其原因就是苹 ...