多线程基础知识及Handler机制(2)
来源:互联网 发布:数控平面钻的编程 编辑:程序博客网 时间:2024/06/11 19:10
Handler机制
Handler 、 Looper 、Message 这三者都是与Android异步消息处理线程相关的概念,那么什么叫异步消息处理线程呢?
- 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环,若消息队列为空,线程则会阻塞等待。
- 这样做的好处就是消息的发送方只要把消息发送到应用程序的消息队列中去就行了,它可以马上返回去处理别的事情,而不需要等待消息的接收方去处理完这个消息才返回,这样就可以提高系统的并发性。
先给大家介绍下Handler机制的几个重要概念:
UI线程 : 就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue。
Looper :消息循环,每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理。
Handler : 发送与处理信息,如果希望Handler正常工作,在当前线程中要有一个Looper对象,驾驭整个消息系统模型。
MessageQueue :消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue。
Message : Handler接收与处理的消息对象,包含消息描述和数据。
Looper:
1. Looper是线程用来运行消息循环(message loop)的类。默认情况下,线程并没有与之关联的Looper,可以通过在线程中调用Looper.prepare() 方法来获取,并通过Looper.loop() 无限循环地获取并分发MessageQueue中的消息,直到所有消息全部处理,与外部的交互通过Handler进行。
2. App主线程就是一个Looper线程,会在循环中不断等其他线程给它发消息(消息包括:Activity启动,生命周期,更新UI,控件事件等),一有消息就根据消息做相应的处理,Looper的另外一部分工作就是在循环代码中会不断从消息队列挨个拿出消息给主线程处理。
不啰嗦了,上代码:
public final class Looper { static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //ThreadLocal:维护线程的变量,为每个使用该变量的线程实例提供独立的变量副本,每个线程都能够独立使用该变量,而互不影响。 //所以每一个线程调用Looper.prepare时,都会创建为其唯一的Looper。 // sThreadLocal 是static的变量,可以先简单理解它相当于map,key是线程,value是Looper,那么你只要用当前的线程就能通过sThreadLocal获取当前线程所属的Looper。 final MessageQueue mQueue; //Looper 所属的线程的消息队列 final Thread mThread; //Looper 所属的线程 private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } public static @Nullable Looper myLooper() { return sThreadLocal.get(); } public @NonNull Thread getThread() { return mThread; } public void quit() { mQueue.quit(false); } //这个方法非常重要 public static void loop() { final Looper me = myLooper(); //获取sThreadLocal 存储的Looper对象,也就是当前线程的Looper final MessageQueue queue = me.mQueue; //获取Looper对象中的消息队列 ... for (;;) { //主线程开启无限循环模式 Message msg = queue.next(); // 获取队列中的下一条消息,可能会线程阻塞 if (msg == null) { //没有消息,则退出循环 return; } //分发Message,msg.target 是一个Handler对象,哪个Handler把这个Message发到队列里, //这个Message会持有这个Handler的引用,并放到自己的target变量中,这样就可以回调我们重写 //的handler的handleMessage方法。 msg.target.dispatchMessage(msg); msg.recycleUnchecked(); //将Message回收到消息池,下次要用的时候不需要重新创建,obtain()就可以了。}
这时候线程执行到这一步就进入了死循环,不断地去拿消息队列里面的消息出来处理?那么问题来了(这两个问题是后话,可以先跳过)
1、UI线程一直在这个循环里跳不出来,主线程不会因为Looper.loop()里的死循环卡死吗,那还怎么执行其他的操作呢?
在looper启动后,主线程上执行的任何代码都是被looper从消息队列里取出来执行的。也就是说主线程之后都是通过其他线程给它发消息来实现执行其他操作的。生命周期的回调也是如此的,系统服务ActivityManagerService通过Binder发送IPC调用给APP进程,App进程接到到调用后,通过App进程的Binder线程给主线程的消息队列插入一条消息来实现的。
2、主线程是UI线程和用户交互的线程,优先级应该很高,主线程的死循环一直运行是不是会特别消耗CPU资源吗?App进程的其他
线程怎么办?
这基本是一个类似生产者消费者的模型,简单说如果在主线程的MessageQueue没有消息时,就会阻塞在loop的queue.next()方法里,这时候主线程会释放CPU资源进入休眠状态,直到有下个消息进来时候就会唤醒主线程,它的实现涉及到Linux pipe/epoll机制,通过往pipe管道写端写入数据来唤醒主线程工作。原理类似于I/O,读写是堵塞的,不占用CPU资源。
Looper主要作用,总结几点:
1、我很啰嗦。
2、与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
3、loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
到现在,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息looper,是时候需要个伙伴来驱动他们了,那么请继续看主角hanlder。
Hanlder
使用Handler之前,我们都是初始化一个实例,比如用于更新UI线程,所以我们首先看Handler的构造方法,看其如何与MessageQueue联系上的,它在子线程中发送的消息(一般发送消息都在非UI线程)怎么发送到MessageQueue中的。
public Handler(Callback callback, boolean async) { mLooper = Looper.myLooper(); // 默认将关联当前线程的looper,sThreadLocal.get(); if (mLooper == null) { //当前线程不是Looper 线程,没有调用Looper.prepare()给线程创建Looper对象 throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; //让Handler 持有当前线程消息队列的引用,把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上 mCallback = callback; //这些callback先不管,主要用于handler的消息发送的回调,优先级是比handlerMessage高,但是不常用 mAsynchronous = async; }
我们继续看一下我们常用的几个方法,
1) post(Runnable), 将runnable对象入队,将runnable封装成为Message,最终也是调用的sendMessageAtTime 2) postAtTime(Runnable, long), 将runnable对象入队,并在指定时间执行 3) postDelayed(Runnable, long), 将runnable对象入队,并经过指定时间后执行 4) sendEmptyMessage(int), 发送只具有what标志值得message 5) sendMessage(Message), 将一个message对象入队,且允许该message对象带有一些数据,如一个bundle类型的数据或一个int类型的标志值等,这些数据将在Handler的handleMessage(Message) 方法中进行处理,当然,具体处理逻辑需要我们自己重写handleMessage()方法 6) sendMessageAtTime(Message, long), 将message入队,并在指定时间到之前将该消息放在所有挂起的消息之后 7) sendMessageDelayed(Message, long), 将message入队,并在当前时间延迟指定时间长度前将该消息放在所有挂起的消息之后
以上便是比较常用的方法,上面的所有方法最终调用的都是sendMessageAtTime(Message, long) 这个方法(如果传入的参数是runnable的话,会先调用getPostMessage(Runnable)) ,然后由sendMessageAtTime(Message, long) 为message对象指定target为该handler实例,并返回一个enqueueMessage(MessageQueue, Message, long) 方法,最终通过MessageQueue的
enqueueMessage(Message, long) 将消息成功送进消息队列中。
在看看发消息的函数
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; //初始化handler的时候,Looper.myLooper().mQueue if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); return false; } return enqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; //这句话很重要,让消息持有当前Handler的引用,在消息被Looper线程轮询到的时候回调handler的 handleMessage方法。 ... return queue.enqueueMessage(msg, uptimeMillis); //调用MessageQueue 的enqueueMessage 方法把消息放入队列 } //可以看出post 的Runnable 也是封装成message发送的 public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
其他方法就不罗列了,为什么呢,因为我也没有仔细研究,总之通过handler发出的message有如下特点:
1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码
msg.target.dispatchMessage(msg);
2.post发出的message,其callback为Runnable对象
Handler处理消息
说完了消息的发送,再来看下handler如何处理消息。消息的处理是通过核心方法dispatchMessage(Message msg)完成的,老规矩,上代码:
// 处理消息,该方法由looper调用 public void dispatchMessage(Message msg) { if (msg.callback != null) { // 如果message设置了callback,即runnable消息,处理callback! handleCallback(msg); } else { // 如果handler本身设置了callback,则执行callback if (mCallback != null) { /* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。 if (mCallback.handleMessage(msg)) { return; } } // 如果message没有callback,则调用handler的handleMessage handleMessage(msg); } } // 处理runnable消息 private final void handleCallback(Message message) { message.callback.run(); //直接调用run方法! } // 需要实现处理消息 public void handleMessage(Message msg) { }
稍作总结:
1、Handler 对象在哪个线程下构建(Handler的构造函数在哪个线程下调用),那么Handler 就会持有这个线程的 Looper引用和这个线程的消息队列的引用。因为持有这个线程的消息队列的引用,意味着这个Handler对象可以在任意其他线程给该线程的消息队列添加消息,也意味着Handler的handlerMessage 肯定也是在该线程执行的。
2、如果该线程不是Looper线程,在这个线程new Handler 就会报错!
3、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
4、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。
5、
Message的回调方法优先级最高,即message.callback.run();
Handler的回调方法优先级次之,即Handler.mCallback.handleMessage(msg);
Handler的默认方法优先级最低,即Handler.handleMessage(msg)。
上图,有图有真相嘛,声明下图都不是自己画的,网上各路去找的,出处早已忘了。
MessageQueue
那我们继续再看一下到底是怎么从消息队列里面取的,直接上代码:
Message next() { final long ptr = mPtr; //mPtr = nativeInit(); //nextPollTimeoutMillis 表示nativePollOnce方法需要等待nextPollTimeoutMillis才会返回 int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //读取消息,队里里没有消息有可能会堵塞,两种情况该方法才会返回(代码才能往下执行) //一种是等到有消息产生就会返回, //另一种是当等了nextPollTimeoutMillis时长后,nativePollOnce也会返回 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // 循环找到一条不是异步而且msg.target不为空的message do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // 虽然有消息,但是还没有到运行的时候,像我们经常用的postDelay,计算出离执行时间还有多久赋值给nextPollTimeoutMillis,表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 获取到消息 mBlocked = false; //链表一些操作,获取msg并且删除该节点 if (prevMsg != null) prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); //返回拿到的消息 return msg; } } else { //没有消息,nextPollTimeoutMillis复位 nextPollTimeoutMillis = -1; } }
直接看正常取消息的部分,其实就是取出单链表(我们前面已说过,MessageQueue其实是
一个单链表结构)中的头结点,然后修改对应指针,再返回取到的头结点而已。
nativePollOnce()很重要,是一个native的函数,在native做了大量的工作,主要涉及到epoll机制的处理(在没有消息处理时阻塞在管道的读端),分析到这里,从应用启动创建Looper,创建消息队列,到进入loop方法执行无限循环中,那么这一块就告一段落了,主线程已经在死循环里轮询等待消息了,接下来我们就要再看看,系统是怎么发消息给主线程的,主线程是怎么处理这些个消息的?
boolean enqueueMessage(Message msg, long when) { // msg 必须有target也就是必须有handler if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } //插入消息队列的时候需要做同步,因为会有多个线程同时做往这个队列插入消息 synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); //when 表示这个消息执行的时间,队列是按照消息执行时间排序的 //如果handler 调用的是postDelay 那么when=SystemClock.uptimeMillis()+delayMillis msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // p==null 表示当前消息队列没有消息 msg.next = p; mMessages = msg; //需要唤醒主线程,如果队列没有元素,主线程会堵塞在管道的读端,这时 //候队列突然有消息了,就会往管道写入字符,唤醒主线程 needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; //将消息放到队列的确切位置,队列是按照msg的when 排序的 for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // 如果需要唤醒Looper线程,这里调用native的方法实现epoll机制唤醒线程 if (needWake) { nativeWake(mPtr); } } return true; }
Message
public final class Message implements Parcelable { //标识消息 public int what; int flags; long when; //传递简单数据 public int arg1; public int arg2; //传递较复杂数据 对象 public Object obj; Bundle data; //处理消息的目标Handler Handler target; //消息派发时 执行的Runnable对象 Runnable callback; //使消息形成链表 Message next; //建立一个消息pool,回收msg,以避免重复创建节约开销 private static Message sPool; private static int sPoolSize = 0; private static final int MAX_POOL_SIZE = 50; }
写到这里基本上关于Handler机制已经讲的差不多了,后面是对消息队列原理的补充,贴了一点Epoll机制的主要代码:
void Looper::wake() {#if DEBUG_POLL_AND_WAKE ALOGD("%p ~ wake", this);#endif ssize_t nWrite; do { nWrite = write(mWakeWritePipeFd, "W", 1); } while (nWrite == -1 && errno == EINTR); if (nWrite != 1) { if (errno != EAGAIN) { ALOGW("Could not write wake signal, errno=%d", errno); } }}void Looper::awoken() {#if DEBUG_POLL_AND_WAKE ALOGD("%p ~ awoken", this);#endif char buffer[16]; ssize_t nRead; do { nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));}Looper::Looper(bool allowNonCallbacks) : mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) { int wakeFds[2]; int result = pipe(wakeFds); mWakeReadPipeFd = wakeFds[0]; mWakeWritePipeFd = wakeFds[1]; mEpollFd = epoll_create(EPOLL_SIZE_HINT); eventItem.data.fd = mWakeReadPipeFd; result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);}int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
参考文档:
http://blog.csdn.net/lmj623565791/article/details/38377229/
http://www.jianshu.com/p/02962454adf7
http://www.mamicode.com/info-detail-517008.html
http://www.cnblogs.com/zrtqsk/p/3776328.html
- 多线程基础知识及Handler机制(2)
- 多线程基础知识及Handler机制(1)
- Android多线程及Handler机制
- java 多线程基础知识2---同步机制
- Android多线程机制之Handler
- Linux基础知识[2]【延迟及定时机制】
- Android多线程及Handler使用
- Handler机制及原理探究
- Android多线程及异步任务消息处理机制(一)--Handler的使用
- 从handler机制看多线程通讯
- 多线程异步机制Handler以及AsyncTask
- Android——多线程(Handler机制)
- Handler机制分析(2)
- Android Handler消息机制原理及总结
- 消息机制Handler及相关源码分析
- Android开发--多线程中的Handler机制/Looper的介绍
- Android多线程中的Handler机制、Looper的介绍与整理
- Android多线程----异步消息处理机制之Handler
- typescript速学:函数、基本数据类型
- 利用scrapy爬取新浪体育上的图片
- WPF 依赖属性
- XML学习---Dom4j学习
- 响铃: AR颠覆美妆生态 美妆相机从相机到社区还有多远?
- 多线程基础知识及Handler机制(2)
- POJ1328 贪心
- 原码、反码、补码和移码
- 浏览器兼容问题
- 伸展树—系列题目
- Problem 1106 Sum of Factorials
- 4.10easyui
- POJ2524 并查集
- 【java】-- 运算符