Looper

Looper是线程用来运行消息循环(message loop)的类。默认情况下,线程并没有与之关联的Looper,可以通过在线程中调用Looper.prepare() 方法来获取,并通过Looper.loop() 无限循环地获取并分发MessageQueue中的消息,直到所有消息全部处理。典型用法如下:

  1. public class LooperThread extends Thread {
  2. @Override
  3. public void run() {
  4. // 将当前线程初始化为Looper线程
  5. Looper.prepare();
  6.  
  7. // ...其他处理,如实例化handler
  8.  
  9. // 开始循环处理消息队列
  10. Looper.loop();
  11. }
  12. }

通过上面两行核心代码,你的线程就升级为Looper线程了

1)Looper.prepare()

通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MessageQueue。一个Thread只能有一个Looper对象

  1. public class Looper {
  2. // 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
  3. private static final ThreadLocal sThreadLocal = new ThreadLocal();
  4. // Looper内的消息队列
  5. final MessageQueue mQueue;
  6. // 当前线程
  7. Thread mThread;
  8. // 。。。其他属性
  9.  
  10. // 每个Looper对象中有它的消息队列,和它所属的线程
  11. private Looper() {
  12. mQueue = new MessageQueue();
  13. mRun = true;
  14. mThread = Thread.currentThread();
  15. }
  16.  
  17. // 我们调用该方法会在调用线程的TLS中创建Looper对象
  18. public static final void prepare() {
  19. if (sThreadLocal.get() != null) {
  20. // 试图在有Looper的线程中再次创建Looper将抛出异常
  21. throw new RuntimeException("Only one Looper may be created per thread");
  22. }
  23. sThreadLocal.set(new Looper());
  24. }
  25. // 其他方法

通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal。如果你还不清楚什么是ThreadLocal,请参考《理解ThreadLocal》

2)Looper.loop()

调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。其源码分析如下:

  1. public static final void loop() {
  2. Looper me = myLooper(); //得到当前线程Looper
  3. MessageQueue queue = me.mQueue; //得到当前looper的MQ
  4.  
  5. // 这两行没看懂= = 不过不影响理解
  6. Binder.clearCallingIdentity();
  7. final long ident = Binder.clearCallingIdentity();
  8. // 开始循环
  9. while (true) {
  10. Message msg = queue.next(); // 取出message
  11. if (msg != null) {
  12. if (msg.target == null) {
  13. // message没有target为结束信号,退出循环
  14. return;
  15. }
  16. // 日志。。。
  17. if (me.mLogging!= null) me.mLogging.println(
  18. ">>>>> Dispatching to " + msg.target + " "
  19. + msg.callback + ": " + msg.what
  20. );
  21. // 非常重要!将真正的处理工作交给message的target,即后面要讲的handler
  22. msg.target.dispatchMessage(msg);
  23. // 还是日志。。。
  24. if (me.mLogging!= null) me.mLogging.println(
  25. "<<<<< Finished to " + msg.target + " "
  26. + msg.callback);
  27.  
  28. // 下面没看懂,同样不影响理解
  29. final long newIdent = Binder.clearCallingIdentity();
  30. if (ident != newIdent) {
  31. Log.wtf("Looper", "Thread identity changed from 0x"
  32. + Long.toHexString(ident) + " to 0x"
  33. + Long.toHexString(newIdent) + " while dispatching to "
  34. + msg.target.getClass().getName() + " "
  35. + msg.callback + " what=" + msg.what);
  36. }
  37. // 回收message资源
  38. msg.recycle();
  39. }
  40. }
  41. }

除了prepare()和loop()方法,Looper类还提供了一些有用的方法,比如

Looper.myLooper()得到当前线程looper对象:

  1. public static final Looper myLooper() {
  2. // 在任意线程调用Looper.myLooper()返回的都是那个线程的looper
  3. return (Looper)sThreadLocal.get();
  4. }

getThread()得到looper对象所属线程:

  1. public Thread getThread() {
  2. return mThread;
  3. }
  1.  

Handler 避免内存泄漏

  1. handler.removeCallbacksAndMessages(null);
  2. handler.getLooper().quit();
  1. Looperquit()方法结束looper循环:
  1. public final class Looper {// sThreadLocal.get() will return null unless you've called prepare().
  2. static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  3. private static Looper sMainLooper; // guarded by Looper.class
  4.  
  5. final MessageQueue mQueue;
  6.  
  7. public void quit() {
  8. mQueue.quit(false);//调用MessageQueue退出
  9. }
    }
  1. MessageQueue quit()方法
  1. public final class MessageQueue {
  2. //...
  3. void quit(boolean safe) {
  4. if (!mQuitAllowed) {
  5. throw new IllegalStateException("Main thread not allowed to quit.");
  6. }
  7.  
  8. synchronized (this) {
  9. if (mQuitting) {
  10. return;
  11. }
  12. mQuitting = true;
  13.  
  14. if (safe) {
  15. removeAllFutureMessagesLocked();
  16. } else {
  17. removeAllMessagesLocked();
  18. }
  19.  
  20. // We can assume mPtr != 0 because mQuitting was previously false.
  21. nativeWake(mPtr);
  22. }
  23. }
  24.  
  25. private void removeAllMessagesLocked() {
  26. Message p = mMessages;
  27. while (p != null) {
  28. Message n = p.next;
  29. p.recycleUnchecked();
  30. p = n;
  31. }
  32. mMessages = null;
  33. }
  34.  
  35. private void removeAllFutureMessagesLocked() {
  36. final long now = SystemClock.uptimeMillis();
  37. Message p = mMessages;
  38. if (p != null) {
  39. if (p.when > now) {
  40. removeAllMessagesLocked();
  41. } else {
  42. Message n;
  43. for (;;) {
  44. n = p.next;
  45. if (n == null) {
  46. return;
  47. }
  48. if (n.when > now) {
  49. break;
  50. }
  51. p = n;
  52. }
  53. p.next = null;
  54. do {
  55. p = n;
  56. n = p.next;
  57. p.recycleUnchecked();
  58. } while (n != null);
  59. }
  60. }
  61. }
  62.  
  63. //...
  64. }

通过观察以上源码我们可以发现:
当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。
当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。
无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了

到此为止,你应该对Looper有了基本的了解,总结几点:

1.每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal

2.Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行

3.Looper使一个线程变成Looper线程。

handler

什么是handler?handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。默认的构造方法:

  1. public class handler {
  2.  
  3. final MessageQueue mQueue; // 关联的MQ
  4. final Looper mLooper; // 关联的looper
  5. final Callback mCallback;
  6. // 其他属性
  7.  
  8. public Handler() {
  9. // 没看懂,直接略过,,,
  10. if (FIND_POTENTIAL_LEAKS) {
  11. final Class extends Handler> klass = getClass();
  12. if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
  13. (klass.getModifiers() & Modifier.STATIC) == 0) {
  14. Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
  15. klass.getCanonicalName());
  16. }
  17. }
  18. // 默认将关联当前线程的looper
  19. mLooper = Looper.myLooper();
  20. // looper不能为空,即该默认的构造方法只能在looper线程中使用
  21. if (mLooper == null) {
  22. throw new RuntimeException(
  23. "Can't create handler inside thread that has not called Looper.prepare()");
  24. }
  25. // 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
  26. mQueue = mLooper.mQueue;
  27. mCallback = null;
  28. }
  29.  
  30. // 其他方法
  31. }

 这里有一个疑问,如果handler在主线程的死循环一直运行是不是特别消耗CPU资源呢?

这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

下面我们就可以为之前的LooperThread类加入Handler:

  1. public class LooperThread extends Thread {
  2. private Handler handler1;
  3. private Handler handler2;
  4.  
  5. @Override
  6. public void run() {
  7. // 将当前线程初始化为Looper线程
  8. Looper.prepare();
  9.  
  10. // 实例化两个handler
  11. handler1 = new Handler();
  12. handler2 = new Handler();
  13.  
  14. // 开始循环处理消息队列
  15. Looper.loop();
  16. }
  17. }

可以看到,一个线程可以有多个Handler,但是只能有一个Looper!

Looper,Handler, MessageQueue的更多相关文章

  1. Android开发之漫漫长途 ⅥI——Android消息机制(Looper Handler MessageQueue Message)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  2. Android开发之漫漫长途 Ⅶ——Android消息机制(Looper Handler MessageQueue Message)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  3. Looper: Looper,Handler,MessageQueue三者之间的联系

    在Android中每个应用的UI线程是被保护的,不能在UI线程中进行耗时的操作,其他的子线程也不能直接进行UI操作.为了达到这个目的Android设计了handler Looper这个系统框架,And ...

  4. Looper Handler MessageQueue Message 探究

    Android消息处理的大致的原理如下: 1.有一个消息队列,可以往队列中添加消息 2.有一个消息循环,可以从消息队列中取出消息 Android系统中这些工作主要由Looper和Handler两个类来 ...

  5. Android消息处理机制(Handler、Looper、MessageQueue与Message)

    Android是消息驱动的,实现消息驱动有几个要素: 消息的表示:Message 消息队列:MessageQueue 消息循环,用于循环取出消息进行处理:Looper 消息处理,消息循环从消息队列中取 ...

  6. (转)Android消息处理机制(Handler、Looper、MessageQueue与Message)

    转自 http://www.cnblogs.com/angeldevil/p/3340644.html Android消息处理机制(Handler.Looper.MessageQueue与Messag ...

  7. [Android]Message,MessageQueue,Looper,Handler详解+实例

    转http://www.eoeandroid.com/forum-viewthread-tid-49595-highlight-looper.html 一.几个关键概念 1.MessageQueue: ...

  8. [转]Handler MessageQueue Looper消息循环原理分析

    Handler MessageQueue Looper消息循环原理分析   Handler概述 Handler在Android开发中非常重要,最常见的使用场景就是在子线程需要更新UI,用Handler ...

  9. Handler与Looper,MessageQueue的关系

    总结一下Handler与Looper,MessageQueue的关系,并实现自定义与子线程相关的Handler. 一.Handler与Looper,MessageQueue的关系 它们之间的关系其实就 ...

随机推荐

  1. Consul集群加入网关服务(Spring Cloud Gateway)

    Consul集群加入网关服务 架构示意图 外部的应用或网站通过外部网关服务消费各种服务,内部的生产者本身也可能是消费者,内部消费行为通过内部网关服务消费. 一个内部网关和一个外部网关以及一个Consu ...

  2. 把java(springboot)程序打包docker镜像

    前言:要在docker运行java(jar包)程序,就要把程序打包成docker镜像(以下简称镜像),可以先理解为镜像就是jar包 打包需要程序代码,java本身的打包环境(包括jdk和maven), ...

  3. Linux 使用ansible配置集群间互信

    安装pip $ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py # 下载安装脚本 $ sudo python get-pip.py # ...

  4. Sublime text设置快捷键让编写的HTML文件在打指定浏览器预览

    作者:浪人链接:https://www.zhihu.com/question/27219231/answer/43608776来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出 ...

  5. [Vue warn]: You may have an infinite update loop in a component render function

    [Vue warn]: You may have an infinite update loop in a component render function 这个问题很奇怪,之前从来没有遇到过.如果 ...

  6. SSM商城系统开发笔记-配置01-web.xml

    先占坑 慢慢填, 商城系统使用主体框架:Spring + Spring MVC + Mybatis 其他框架: 日志: slf4j + logback <!DOCTYPE web-app PUB ...

  7. Flutter 实际开发常用工具类(全局提示,请求封装,token缓存,验证码倒计时、常用窗帘动画及布局)

    介绍: 一星期从入门到实际开发经验分享及总结           代码传送门github Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面.未来App开发 ...

  8. R语言封装函数

    R语言封装函数 原帖见豆瓣:https://www.douban.com/note/279077707/ 一个完整的R函数,需要包括函数名称,函数声明,函数参数以及函数体几部分. 1. 函数名称,即要 ...

  9. 一、VS支持Vue语法

    一.VS支持Vue语法

  10. 树莓派 msmtp和mutt 的安装和配置

    1,安装mutt sudo apt-get install mutt 2,安装msmtp sudo apt-get install msmtp 3,设置mutt /etc/Muttrc # 系统全局设 ...