Handler的消息运行处理机制
2016-12-06 15:10
603 查看
一、Handler运行机制的总体流程
由上图可以看出,对于handler处理消息的流程是:
①:产生消息:消息队列中产生了新的消息 ②:获取消息:创建handler对象,获取到要发送的消息 ③:发送消息:通过handler机制,将消息发送到looper的队列中 ④:处理消息:looper的轮循机制,不断的扫描查看是否有新的消息产生,如果发现有新的消息产生,那么就调用该handler对应的handler处理方法,进行消息处理。
对于handler处理消息的机制,大致就是以上几个过程,当然在执行流程之前,我们已经建立了Handler和Looper的对象。下面我们就从源码的角度详细的分析整个执行过程。
二、Handler关联Looper对象
单链表结构是怎样形成的呢?这个就是由我们的消息的创建机制来完成的。主要是实现把消息池里的第一条数据取出,当消息池中有消息时,这时我们就需要调用handler对象来进行获取消息和发送消息,那么hanlder的对象究竟是怎样获取和发送消息的呢?这时我们就需要查看源码了。/** * Default constructor associates this handler with the queue for the * current thread. * * If there isn't one, this handler won't be able to receive messages. */ 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()); } } //对于上面的条件判断,对于我们分析运行机制没有多少帮助,这里我们可以暂时忽视,着重分析下面的代码。 mLooper = Looper.myLooper();//调用myLooper()方法获取looper对象 if (mLooper == null) {//如果mLooper为空,就会报运行时异常,导致程序崩掉。 throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue;//拿到消息队列 mCallback = null;//将回调对象设置为空。 }
通过上面的源码分析,我们可以看出,Handler主要是完成了获取looper对象和拿到消息队列。那么究竟是怎样获取到这两个对象的呢?我们还是要从源码角度分析:
/** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static final Looper myLooper() { return (Looper)sThreadLocal.get();//通过sThreadLocal的get方法获取到looper对象 }
通过上面的源码分析,我们可以看出静态方法myLooper()主要是通过get方法获取到looper对象,既然有get方法,那么肯定会存在一个set方法,将looper对象设置进去,继续跟踪源码。
/** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ public static final void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); } 从上面的源码中我们可以看出Looper是直接new出来的,并且继续跟踪源码我们发现在Looper的构造方法中,new出了消息队列对象 private Looper() { mQueue = new MessageQueue();//创建消息队列 mRun = true; mThread = Thread.currentThread();//获取到当前线程对象 }
既然ThreadLocal.set(new Looper())方法是Looper.prepare()调用的,那么,prepare()又是什么时候调用的呢?继续查看源码,会发现是prepareMainLooper()调用的,至此我们便查看到了整个looper的创建和调用机制,如果继续在Looper类中查找我们,很难发现什么时候调用prepareMainLooper()方法,这个时候我们需要查看ActivityThread类,在其main()方法中我们会发现该方法,下面我们对该方法继续分析:
public static final void main(String[] args) { ... //创建Looper和MessageQueue Looper.prepareMainLooper(); ... //轮询器开始轮询 Looper.loop(); ... ///如果上面的looper结束,那么在此立刻就会抛异常,程序停止 }
三、Looper的轮循机制
细心的哥们会发现,对于Looper的轮循是通过调用Looper.loop()实现的,该方法中具体是实现那些事情呢?我们查看源码一起分析一下。/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static final void loop() { Looper me = myLooper();//获取looper对象 MessageQueue queue = me.mQueue;//获取队列 while (true) {//死循环,不断的轮循 Message msg = queue.next(); < 4000 span class="hljs-comment">// 取出下一个消息 //if (!me.mRun) { // break; //} if (msg != null) {//如果消息不为空 if (msg.target == null) { // No target is a magic identifier for the quit message. return; } if (me.mLogging!= null) me.mLogging.println( ">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what ); msg.target.dispatchMessage(msg);//分发消息 if (me.mLogging!= null) me.mLogging.println( "<<<<< Finished to " + msg.target + " " + msg.callback); msg.recycle();//回收消息 } } }
这里面通过一个死循环来不断的轮序消息队列,查看是否有消息的产生,如果有消息产生,就分发消息,然后轮循消息。有哥们会问“这个处理是在主线程中,不会出现ANR吗?”,针对于这一点我们详细分析一下。
为什么采用死循环?
我们都知道hanlder的处理机制中一个重要的环节就是通过建立Looper的机制,不断的轮循消息队列,查看是否有消息产生,如果有,就将消息去除,交给对应的handler方法处理(message对象的target属性,用于记录该消息由哪个Handler创建,在obtain方法中赋值),如果不是采用死循环的形式,我们无法保证looper循环的连贯性。
ANR产生的原因
在主线程中做耗时操作,肯定会产生”ANR”,在我们该方法中,主要可能产生“ANR”的是msg.target.dispatchMessage(msg),因为对于消息的分发处理,如果某个消息是一个耗时的操作,那么就有可能产生“ANR”,导致阻塞对于下一个消息的处理。
怎样解决ANR
假如我在oncreate()开启一个耗时操作,如果是在里面直接开启耗时,那么就会阻塞其他方法的执行(onstart()),其他的消息就无法正常的执行和处理,但是如果我们在此时开启一个子线程去处理oncreate()中的方法,那么oncreate()就会立即结束,主线程就会执行其他的方法中,不会产生ANR,当oncreate()的耗时操作,执行结束后,通过sendMessage()方法,将消息发送给主线程,主线程收到消息后再处理。这样整个流程就会正常执行。
解决死循环的机制
Linux的一个进程间通信机制:管道(pipe)。原理:在内存中有一个特殊的文件,这个文件有两个句柄(引用),一个是读取句柄,一个是写入句柄,主线程Looper从消息队列读取消息,当读完所有消息时,进入睡眠,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。
四、Message的队列维护和发送
我们提到了Message的是单链表结构,那么这种结构究竟是怎样产生的呢?我们继续跟踪源码去查看一下。Handler发送消息,sendMessage的所有重载,实际最终都调用了sendMessageAtTimepublic boolean sendMessageAtTime(Message msg, long uptimeMillis) { boolean sent = false; MessageQueue queue = mQueue; if (queue != null) { msg.target = this; sent = queue.enqueueMessage(msg, uptimeMillis); } else { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); } return sent; }
分析源码可以看出,对于消息的队列维护的处理,主要是通过sent = queue.enqueueMessage(msg, uptimeMillis)方法完成的,其中两个参数分别表示的是消息对象和时间戳。下面仔细分析一下这个加载维护过程。
4.1消息入列
注意观察msg.when = when;其中when非常重要,它是消息的取出和回收,以及判断消息是否处理过的重要标志。synchronized (this) { ... //对消息的重新排序,通过判断消息队列里是否有消息以及消息的时间对比 msg.when = when;//这个when非常重要,它是消息的取出和回收,以及判断消息是否处理过的重要标志 Message p = mMessages; //把放入消息队列的消息置为消息队列第一条消息 if (p == null || when == 0 || when < p.when) {//注意when,时间早的放在最前面 msg.next = p;//mMessages指向该消息对象 mMessages = msg;//指向新的消息 needWake = mBlocked; // new head, might need to wake up//是否换到主线程 } else {//下个消息.... //判断时间顺序,为刚放进来的消息寻找合适的位置 Message prev = null; while (p != null && p.when <= when) { prev = p; p = p.next; } msg.next = prev.next; prev.next = msg; needWake = false; // still waiting on head, no need to wake up } }
第一个消息进入
如图所示,当我们要加载一个消息(每个消息都包含when 和next属性)进入队列时,mMessages指向该消息,直接加入到队列中。
再次有消息进入
再次有消息进入时,会将mMessages赋值给临时变量p,那么p就指向了队列中的消息对象。此时就开始比较when的值(假如新加入的消息when小于队列中的,走条件一),注意下面的赋值变换:
msg.next = p;//p指向新来的消息的next
mMessages = msg;//新来的消息指向队列中的message。
同理走else条件时,仍然是根据when条件进行判断,从而形成了单链表结构。
4.2 回收消息
/** * Return a Message instance to the global pool. You MUST NOT touch * the Message after calling this function -- it has effectively been * freed. */ public void recycle() { synchronized (mPoolSync) { if (mPoolSize < MAX_POOL_SIZE) { clearForRecycle();//调用消息的方法 next = mPool;//原先指向队列中消息的mpool指向新进入的消息的next mPool = this;//将当前对象赋值给mpool } } }
实现原理:如果当前队列中的mPoolSize 小于队列能承受的最大值,就将消息添加到message的队列中,否则就将消息丢掉,交给垃圾回收机制处理。
如果符合条件,就将原来指向队列中的mPool赋值刚进入的next,新加入的消息对象赋值给mPool。
4.2获取消息
获取消息的机制如下图所示:源码分析
/** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ public static Message obtain() { synchronized (mPoolSync) { if (mPool != null) { Message m = mPool; mPool = m.next; m.next = null; return m; } } return new Message(); }
obtain()方法中mPool(临时的Message对象)判断是否为空,如果不为空,则进入内部的判断。具体实现原理解析:
- Message m = mPool;//临时变量m指向第一个消息对象
- mPool = m.next;//mPool 指向下一个对象
- m.next = null;//m的next为空,就是将其断开
这样就完成了消息队列的取出机制。
至此我们就完成了整个的handler消息产生、发送、looper的轮循处理、交给对应handler方法处理等过程。
总结:主线程中有一个Looper机制里面包含一个while死循环,这个死循环运行的原理以及解决ANR的方法;创建Handler怎样和Looper进行关联的。其次讲解了从Message队列中怎样获取到Message消息,获取到消息后是怎样通过handler的机制加入到Looper的可执行队列中的(按when排序)。最后我们是怎样调用对应的Handler方法执行的。
备注:码农小白,水平有限,如有错误欢迎指正。
相关文章推荐
- Python动态类型的学习---引用的理解
- 土人系列AS入门教程 -- 对象篇
- C#托管堆对象实例包含内容分析
- java之File对象对文件的操作常用的几个方法(推荐)
- C#实现获取不同对象中名称相同属性的方法
- javascript asp教程第十一课--Application 对象
- PowerShell中使用Out-String命令把对象转换成字符串输出的例子
- VBS教程:对象-正则表达式(RegExp)对象
- C#检查指定对象是否存在于ArrayList集合中的方法
- sql2008启动代理未将对象应用到实例解决方案
- 详解SQL Server数据库架构和对象、定义数据完整性
- C#编程自学之类和对象
- C++中对象的常引用、动态建立和释放相关知识讲解
- C++之类和对象课后习题简单实例
- 深入理解PHP JSON数组与对象
- php中将一个对象保存到Session中的方法
- php对象和数组相互转换的方法
- PHP中把对象转换为关联数组代码分享
- C#写入对象或集合类型数据到xml文件的方法
- C#利用反射来判断对象是否包含某个属性的实现方法