Looper,Handler, MessageQueue
Looper
Looper是线程用来运行消息循环(message loop)的类。默认情况下,线程并没有与之关联的Looper,可以通过在线程中调用Looper.prepare()
方法来获取,并通过Looper.loop()
无限循环地获取并分发MessageQueue中的消息,直到所有消息全部处理。典型用法如下:
- public class LooperThread extends Thread {
- @Override
- public void run() {
- // 将当前线程初始化为Looper线程
- Looper.prepare();
- // ...其他处理,如实例化handler
- // 开始循环处理消息队列
- Looper.loop();
- }
- }
通过上面两行核心代码,你的线程就升级为Looper线程了
1)Looper.prepare()
通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MessageQueue。一个Thread只能有一个Looper对象
- public class Looper {
- // 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
- private static final ThreadLocal sThreadLocal = new ThreadLocal();
- // Looper内的消息队列
- final MessageQueue mQueue;
- // 当前线程
- Thread mThread;
- // 。。。其他属性
- // 每个Looper对象中有它的消息队列,和它所属的线程
- private Looper() {
- mQueue = new MessageQueue();
- mRun = true;
- mThread = Thread.currentThread();
- }
- // 我们调用该方法会在调用线程的TLS中创建Looper对象
- public static final void prepare() {
- if (sThreadLocal.get() != null) {
- // 试图在有Looper的线程中再次创建Looper将抛出异常
- throw new RuntimeException("Only one Looper may be created per thread");
- }
- sThreadLocal.set(new Looper());
- }
- // 其他方法
通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal。如果你还不清楚什么是ThreadLocal,请参考《理解ThreadLocal》
2)Looper.loop()
调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。其源码分析如下:
- public static final void loop() {
- Looper me = myLooper(); //得到当前线程Looper
- MessageQueue queue = me.mQueue; //得到当前looper的MQ
- // 这两行没看懂= = 不过不影响理解
- Binder.clearCallingIdentity();
- final long ident = Binder.clearCallingIdentity();
- // 开始循环
- while (true) {
- Message msg = queue.next(); // 取出message
- if (msg != null) {
- if (msg.target == null) {
- // message没有target为结束信号,退出循环
- return;
- }
- // 日志。。。
- if (me.mLogging!= null) me.mLogging.println(
- ">>>>> Dispatching to " + msg.target + " "
- + msg.callback + ": " + msg.what
- );
- // 非常重要!将真正的处理工作交给message的target,即后面要讲的handler
- msg.target.dispatchMessage(msg);
- // 还是日志。。。
- if (me.mLogging!= null) me.mLogging.println(
- "<<<<< Finished to " + msg.target + " "
- + msg.callback);
- // 下面没看懂,同样不影响理解
- final long newIdent = Binder.clearCallingIdentity();
- if (ident != newIdent) {
- Log.wtf("Looper", "Thread identity changed from 0x"
- + Long.toHexString(ident) + " to 0x"
- + Long.toHexString(newIdent) + " while dispatching to "
- + msg.target.getClass().getName() + " "
- + msg.callback + " what=" + msg.what);
- }
- // 回收message资源
- msg.recycle();
- }
- }
- }
除了prepare()和loop()方法,Looper类还提供了一些有用的方法,比如
Looper.myLooper()得到当前线程looper对象:
- public static final Looper myLooper() {
- // 在任意线程调用Looper.myLooper()返回的都是那个线程的looper
- return (Looper)sThreadLocal.get();
- }
getThread()得到looper对象所属线程:
- public Thread getThread() {
- return mThread;
- }
Handler 避免内存泄漏
- handler.removeCallbacksAndMessages(null);
- handler.getLooper().quit();
- Looper的quit()方法结束looper循环:
- public final class Looper {// sThreadLocal.get() will return null unless you've called prepare().
- static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
- private static Looper sMainLooper; // guarded by Looper.class
- final MessageQueue mQueue;
- public void quit() {
- mQueue.quit(false);//调用MessageQueue退出
- }
}
- MessageQueue 的quit()方法
- public final class MessageQueue {
- //...
- void quit(boolean safe) {
- if (!mQuitAllowed) {
- throw new IllegalStateException("Main thread not allowed to quit.");
- }
- synchronized (this) {
- if (mQuitting) {
- return;
- }
- mQuitting = true;
- if (safe) {
- removeAllFutureMessagesLocked();
- } else {
- removeAllMessagesLocked();
- }
- // We can assume mPtr != 0 because mQuitting was previously false.
- nativeWake(mPtr);
- }
- }
- private void removeAllMessagesLocked() {
- Message p = mMessages;
- while (p != null) {
- Message n = p.next;
- p.recycleUnchecked();
- p = n;
- }
- mMessages = null;
- }
- private void removeAllFutureMessagesLocked() {
- final long now = SystemClock.uptimeMillis();
- Message p = mMessages;
- if (p != null) {
- if (p.when > now) {
- removeAllMessagesLocked();
- } else {
- Message n;
- for (;;) {
- n = p.next;
- if (n == null) {
- return;
- }
- if (n.when > now) {
- break;
- }
- p = n;
- }
- p.next = null;
- do {
- p = n;
- n = p.next;
- p.recycleUnchecked();
- } while (n != null);
- }
- }
- }
- //...
- }
通过观察以上源码我们可以发现:
当我们调用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的。默认的构造方法:
- public class handler {
- final MessageQueue mQueue; // 关联的MQ
- final Looper mLooper; // 关联的looper
- final Callback mCallback;
- // 其他属性
- public Handler() {
- // 没看懂,直接略过,,,
- 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());
- }
- }
- // 默认将关联当前线程的looper
- mLooper = Looper.myLooper();
- // looper不能为空,即该默认的构造方法只能在looper线程中使用
- if (mLooper == null) {
- throw new RuntimeException(
- "Can't create handler inside thread that has not called Looper.prepare()");
- }
- // 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
- mQueue = mLooper.mQueue;
- mCallback = null;
- }
- // 其他方法
- }
这里有一个疑问,如果handler在主线程的死循环一直运行是不是特别消耗CPU资源呢?
这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
下面我们就可以为之前的LooperThread类加入Handler:
- public class LooperThread extends Thread {
- private Handler handler1;
- private Handler handler2;
- @Override
- public void run() {
- // 将当前线程初始化为Looper线程
- Looper.prepare();
- // 实例化两个handler
- handler1 = new Handler();
- handler2 = new Handler();
- // 开始循环处理消息队列
- Looper.loop();
- }
- }
可以看到,一个线程可以有多个Handler,但是只能有一个Looper!
Looper,Handler, MessageQueue的更多相关文章
- Android开发之漫漫长途 ⅥI——Android消息机制(Looper Handler MessageQueue Message)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Android开发之漫漫长途 Ⅶ——Android消息机制(Looper Handler MessageQueue Message)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Looper: Looper,Handler,MessageQueue三者之间的联系
在Android中每个应用的UI线程是被保护的,不能在UI线程中进行耗时的操作,其他的子线程也不能直接进行UI操作.为了达到这个目的Android设计了handler Looper这个系统框架,And ...
- Looper Handler MessageQueue Message 探究
Android消息处理的大致的原理如下: 1.有一个消息队列,可以往队列中添加消息 2.有一个消息循环,可以从消息队列中取出消息 Android系统中这些工作主要由Looper和Handler两个类来 ...
- Android消息处理机制(Handler、Looper、MessageQueue与Message)
Android是消息驱动的,实现消息驱动有几个要素: 消息的表示:Message 消息队列:MessageQueue 消息循环,用于循环取出消息进行处理:Looper 消息处理,消息循环从消息队列中取 ...
- (转)Android消息处理机制(Handler、Looper、MessageQueue与Message)
转自 http://www.cnblogs.com/angeldevil/p/3340644.html Android消息处理机制(Handler.Looper.MessageQueue与Messag ...
- [Android]Message,MessageQueue,Looper,Handler详解+实例
转http://www.eoeandroid.com/forum-viewthread-tid-49595-highlight-looper.html 一.几个关键概念 1.MessageQueue: ...
- [转]Handler MessageQueue Looper消息循环原理分析
Handler MessageQueue Looper消息循环原理分析 Handler概述 Handler在Android开发中非常重要,最常见的使用场景就是在子线程需要更新UI,用Handler ...
- Handler与Looper,MessageQueue的关系
总结一下Handler与Looper,MessageQueue的关系,并实现自定义与子线程相关的Handler. 一.Handler与Looper,MessageQueue的关系 它们之间的关系其实就 ...
随机推荐
- Consul集群加入网关服务(Spring Cloud Gateway)
Consul集群加入网关服务 架构示意图 外部的应用或网站通过外部网关服务消费各种服务,内部的生产者本身也可能是消费者,内部消费行为通过内部网关服务消费. 一个内部网关和一个外部网关以及一个Consu ...
- 把java(springboot)程序打包docker镜像
前言:要在docker运行java(jar包)程序,就要把程序打包成docker镜像(以下简称镜像),可以先理解为镜像就是jar包 打包需要程序代码,java本身的打包环境(包括jdk和maven), ...
- Linux 使用ansible配置集群间互信
安装pip $ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py # 下载安装脚本 $ sudo python get-pip.py # ...
- Sublime text设置快捷键让编写的HTML文件在打指定浏览器预览
作者:浪人链接:https://www.zhihu.com/question/27219231/answer/43608776来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出 ...
- [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 这个问题很奇怪,之前从来没有遇到过.如果 ...
- SSM商城系统开发笔记-配置01-web.xml
先占坑 慢慢填, 商城系统使用主体框架:Spring + Spring MVC + Mybatis 其他框架: 日志: slf4j + logback <!DOCTYPE web-app PUB ...
- Flutter 实际开发常用工具类(全局提示,请求封装,token缓存,验证码倒计时、常用窗帘动画及布局)
介绍: 一星期从入门到实际开发经验分享及总结 代码传送门github Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面.未来App开发 ...
- R语言封装函数
R语言封装函数 原帖见豆瓣:https://www.douban.com/note/279077707/ 一个完整的R函数,需要包括函数名称,函数声明,函数参数以及函数体几部分. 1. 函数名称,即要 ...
- 一、VS支持Vue语法
一.VS支持Vue语法
- 树莓派 msmtp和mutt 的安装和配置
1,安装mutt sudo apt-get install mutt 2,安装msmtp sudo apt-get install msmtp 3,设置mutt /etc/Muttrc # 系统全局设 ...