优秀的事物果然是常读常新,时隔很久再看消息处理机制,发现了很多之前没有注意到的东西,特此整理。由于是二次整理,这里不再写Handler的一般使用,直接从源码解读开始。
在我浅显的理解里,整个消息机制由Handler、Message、Looper三个主要部分组成,Handler负责发送和处理消息,MessageQuene是按次序存储消息的队列,Looper负责不断的从消息队列中取出消息给Handler的处理端。之前说看源码要找线头,既然消息是从Handler发出的,不妨就试试从Handler开始看。
Handler的构造函数(一)
点开Handler类的源码,可以很快找到Handler的几个构造函数,最后都会调用到下面两个构造函数处:
|
|
先看上面两个参数的构造函数,先看到一坨if判断的代码块,判断条件是FIND_POTENTIAL_LEAKS,看源码注释,这个静态常量是一个标志位,设置为true时可用来检测出继承这个Handler的非静态的内部类或扩展类,当然源码中直接设为了false,这一坨可以先跳过,接下来的一行才是重点:
|
|
mLooper是Handler类成员,通过Looper类的myLooper方法实例化,这也就是Handler的构造方法做的第一件事,实例化一个Looper对象。
点进myLooper方法看一下Looper类在这里做了什么:
|
|
sThreadLocal是ThreadLocal的对象,这里通过sThreadLocal调用了ThreadLocal类的get方法,所以我们继续点进sThreadLocal的get方法:
|
|
sThreadLocal初始化时传入的泛型是Looper类,所以这里的T可以看作是Looper,这个方法中首先拿到了当前的线程,注意加粗的部分,所谓当前线程自然就是调用到此时这个get方法的线程,也就是调用到myLooper方法的线程,也就是初始化Handler对象时调用Handler构造函数的线程。
然后通过getMap方法实例化一个ThreadLocalMap对象,走下去不难发现,map的值为null,再看看setInitialValue方法
|
|
再点进initialValue方法,惊喜的发现返回值还是个null,createMap时也只是将null值放在了用于缓存的map中,最后将null值返回,回到Handler的构造方法中,mLooper = Looper.myLooper(),mLooper得到返回值null,然后抛出异常,程序gg。
惊不惊喜,意不意外。
让我们回头看看Looper类中对sThreadLocal声明时写在上面的注解:
|
|
如果不调用prepare方法,直接调用sThreadLocal的get方法将会返回null,我们从Handler开始走源码,并没有调用到Looper的prepare方法,所以自然会获取null继而无法继续下去。
拆线失败,Handler不是我们要找的线头,上面说要先调用到Looper类的prepare方法才能在调用myLooper方法时不得到null,那就转移视线,这次试试Looper做线头。
Looper的准备工作
Looper的构造方法是私有的,不难发现,调用Looper私有构造方法的地方就是前面一直提到的prepare方法,我们看一下prepare方法:
|
|
无参的prepare方法最后调用到私有的单参prepare方法,方法中对sThreadLocal的get方法做了一个判空,如果返回值不为null则抛出异常,否则才调用set方法通过Looper私有的构造方法新建一个Looper对象添加到sThreadLocal中。
可以看到,通过prepare方法,我们在ThreadLocal对象中放入了一个当前线程的Looper实例,我们之前也尝试性的走了一下handler的构造方法,在走到Looper.myloop方法后,便会因为返回的Looper对象为null而停止,此时有了这个prepare方法,我们就可以在开始真正的loop循环之前成功创建一个handler对象了。
下面是Looper的私有构造函数:
|
|
做了两件事,初始化一个消息队列,以及获取到当前的线程。
所以很明显,prepare方法的主要作用就是初始化一个Looper对象并放到sThreadLocal中,而前面if的判断,则保证了prepare不能被调用两次,否则就会报错。而ThreadLocal的特性是由它创建的变量只能由当前线程访问,综上,prepare方法的作用就是初始化Looper对象,且保证每个线程只有一个Looper对象。
此时再看Looper类的myLooper方法,自然是返回prepare方法中初始化完成并暂存在sThreadLocal中的Looper对象。不过Looper的工作还没完成,Looper的准备工作要做两步,一个是prepare,一个是loop。
|
|
事实上loop方法做了很多log和线程追踪,但是这里只将实现主要功能的代码放在上面。我们看到,方法中先通过myLooper方法获取到前面prepare方法中初始化好的Looper对象,然后拿到Looper的构造函数中初始化好的消息队列对象mQueue,然后进入到了一个无限循环。
循环中不断从消息中取Message对象,如果取到消息为null,则通过return实现阻塞(大意了,这里应该是通过队列对象的next方法实现阻塞),如果有消息出现在队列中,则被取出,通过取出消息的target调用dispatchMessage方法,并将消息传入该方法。
所谓target,其实就是Message类中的一个Handler对象,这里就是拿到队列中取出的消息的Handler属性对象,并调用它的dispatchMesssage方法对这个消息进行分发处理,该方法留到后面看Handler的源码详细分析。
将事件分发处理之后,loop方法最后调用了从队列中取出的Message对象的recycleUnchecked方法:
|
|
显然,这个方法做了一个资源回收的工作,需要注意的是最后加锁的代码段中有一系列pool变量,显然Message类内部有一个变量池的处理,这里不详说,后面Message类中详细解释。
至此,Looper类完成了整个消息处理机制的准备工作,整个Looper类的工作其实也就是先给消息处理机制搭架子,总结起来就是做了下面几件事:
- prepare方法负责初始化一个自己的对象,并确保每个线程只有一个对象;
- 实例化时,构造方法中初始化了消息队列并 绑定了当前的线程;
- loop方法负责开启无限循环,消息队列为空时等待,一旦出现消息就取出并通过消息的target对象进行事件分发;
- 如果取出消息,最后对取出的消息进行资源回收,然后开始下一轮循环
所以如果要在一个自己的线程实现Handler处理消息,一定要做的就是调用Looper类的prepare和loop方法,撘一个消息处理机制的架子出来,如果直接像在主线程一样初始化一个Handler,在下面就直接sendMessage是不行的,因为默认情况下一个子线程是不存在Looper的循环的,而之所以主线程可以这么用是因为主线程默认有消息队列的实现,所以不需调用Looper的prepare和loop方法也可以直接进行消息的发送和处理。
有一个要注意的地方就是,loop方法里是开启了一个无限循环,所以loop方法调用之后,除非循环停止,否则无法运行loop方法后面的代码。
那么问题来了,如何停止一个Looper?
停止一个Looper
前面已经知道,Looper会在loop方法中开启一个无限循环,现在问题是,如果我们在一个activity中开一个新线程,在新线程中为了使用Handler开启了Looper,那么在新线程依赖的activity退出后,由于Looper中有无限循环,新线程无法自己结束,就会造成内存泄漏,这时候就需要Looper中的quit方法了:
|
|
两个方法都调用了MessageQueue类的quit方法,而MessageQueue的quit方法根据传入的boolean值分别调用了removeAllMessagesLocked方法和removeAllFutureMessagesLocked方法,从方法名不难看出,它们一个是直接清除消息队列中所有的消息,一个是清除延迟的消息(通过delay系列方法发送的、当前时间不需要处理的消息),这两个方法调用后,消息队列不再接收消息,Looper也就停止运行。
在新线程中开启Looper后,在适当的地方调用quit方法,可以避免内存泄漏,同时还要注意,如果loop方法后面还有代码,调用quit方法使循环停止后,如果程序没有停止,还可以继续运行loop方法后面的代码。
回到Handler的构造方法
假设现在我们已经在一个需要用到Handler的线程中调用了Looper.prepare(),在调用loop方法之前,我们先初始化一个Handler对象(为什么要在loop方法之前初始化请回头看上文),于是回到了一开始分析的Handler的构造方法:
|
|
这里也是只留下主要代码,我们看到,这里Looper.myLooper()已经可以返回一个当前线程的Looper对象了,然后将Looper对象的队列赋值到当前Handler的消息队列对象上,同时将回调对象和一个标志位也通过构造函数传入的参数赋值。
还有一个三个参数的构造函数,只有在传入的参数中有Looper对象的时候才会最终被调用到:
|
|
也是将传入的三个参数赋值给Handler类中的Looper对象、回调对象、标志位,并将传入的Looper对象的队列赋值给mQueue。
所以Handler的构造函数主要就是做两件事:
- 将传入的参数赋值给自己对应的参数,通过传入的Looper对象或者通过myLooper获取当前线程的Looper对象;
- 通过Looper对象获取消息队列,并赋值给自己的队列属性
到这里,Looper初始化完成,消息队列初始化完成,Handler初始化完成,再调用一下Looper.loop(),开启队列循环,等待消息传入到队列中,一个消息处理机制的架子就搭起来了,接下来该让这个架子运转起来了。
发送消息的Handler
用Handler发送消息,大多数人应该都很熟悉,主要分为两个系列,一个是参数带Runnable接口对象的post系列,另一个是用的比较频繁的send系列。
post系列
其实看下源码,post的系列方法最后调用的还是sendMessage系列方法,但是post方法传入的是Runnable对象,所以中间还有一个getPostMessage方法,方法的作用自然就是将Runnable对象转换为sendMessage系列方法所需要的Message对象:
|
|
转换方法很简单,通过Message的obtain方法获取一个Message对象,将Runnable对象赋值给Message的callback属性,如果有第二个Object对象参数,将Object对象赋值给Message的obj属性,最后将Message返回。
返回后还是调用到了sendMessage系列方法,所以我们继续来看。
sendMessage系列
先看一个经常用的,sendMessage(Message msg)方法,源码中该方法调用了sendMessageDelayed(Message msg, long delayMillis)方法,只不过第二个参数传入了0,相当于发送了一个延迟时间为0的消息,而sendMessageDelayed方法又调用了sendMessageAtTime(Message msg, long uptimeMillis) 方法,第二个参数传入发送消息的绝对时间(从系统启动到消息发送的毫秒数,也就是sendMessageDelayed时的系统时间+delay时间):
|
|
方法中首先拿到了在Handler构造方法中初始化好的消息队列对象mQueue(注意这里的消息队列也就是当前线程的Looper对象中初始化好的消息队列),判断队列对象不为空后,返回enqueueMessage(queue, msg, uptimeMillis)方法的返回值,我们看一下enqueueMessage(queue, msg, uptimeMillis)方法:
|
|
有没有看到熟悉的东西?是的,方法中第一行就对Message的target属性做了赋值操作,将当前的Handler对象引用赋值给了msg的target属性,而这个target属性会在Looper的loop方法中将这个消息从消息队列中取出来之后通过msg.target调用到。
最后调用到的是MessageQueue的enqueueMessage方法,从名字就能看出来,这个方法是MessageQueue用来将消息放入队列的方法,具体是通过对消息对象的when属性(传入的uptimeMillis)的判断进行的队列排序,这里先暂时不展开说,留在MessageQueue中详解。
sendMessage系列的其他方法也是换汤不换药,最后都是通过各种渠道调用到了sendMessageAtTime 方法,稍微有点不同的是sendMessageAtFrontOfQueue方法,它直接调用了enqueueMessage方法,在第三个参数uptimeMillis传入0,所以就像sendMessageAtFrontOfQueue这个方法名所描述的,这个方法会直接将消息对象放到队列的最前端。
总之,经过enqueueMessage方法后,由handler发送的消息通过Handler类中与Looper绑定的MessageQueue对象将消息放到了Looper的消息队列中。而Looper在初始化好后一直在死循环等待消息队列中出现消息,此时队列中出现消息后,Looper的loop方法的死循环中马上通过队列对象的next方法获取到队列中的消息msg,然后通过msg.target.dispatchMessage(msg)来处理消息。
target就是上面在enqueueMessage方法中赋值的Handler对象,所以在发送消息后,最后处理消息的还是Handler。
处理消息的Handler
消息的处理就很直接了,Looper的loop循环中拿到消息后通过消息对象的target属性调用了Handler的dispatchMessage方法,所以我们直接来看这个方法:
|
|
方法很简单,是一个多层的if判断,首先对传入的Message对象的callback属性做了一个判断,如果不为空则调用handleCallback方法:
|
|
前面已经知道,message对象的callback属性是在调用post系列方法发送消息的时候赋值的Runnable对象,所以这个handleCallback方法做的事很简单,就是让传入的Runnable对象运行起来,运行传入的Runnable对象的run方法中的代码。
如果传入的Message对象callback属性为null,则判断mCallback是否为空,mCallback是什么呢?是一个Callback接口对象,该接口定义在Handler内:
|
|
而这个接口对象的赋值,就在前面说到过的Handler的构造函数中,不过我们平时多数用到的构造函数不是前面分析过的两个甚至三个参数的构造函数,而是这个:
|
|
这个构造方法则会最终调用到前面分析的两个参数的构造函数,这也是我们平时写Handler时经常用到的写法的来源:
|
|
如果mCallback对象不为null,则就会调用到传入的Callback对象的handleMessage方法,对应上面的代码,也就会打印出“get msg”的log。
此时有一个细节需要注意,调用接口方法时mCallback.handleMessage(msg)本身是一个if语句的判断条件,而这个方法是有返回值boolean值的,我们再看一下源码:
|
|
很明显,如果mCallback的handleMessage方法返回true,则代码会运行到return,方法就此结束,而如果mCallback的handleMessage方法返回false,则方法还可以继续向下运行,运行到Handler类自己的handleMessage方法!
当然,如果mCallback对象为空,也会直接运行到Handler类自己的handleMessage方法。
我们看一下Handler类自己的handleMessage方法:
|
|
一个空方法,方法上的注释也写的很清楚,这是由子类实现来接收消息的方法,而这也是我们另一个常用的handler写法的来源:
|
|
不知道这么多弯弯绕绕看晕了没有,简单来说,接收到消息的Handler,根据传入的Message的callback属性和自己的mCallback属性是否为空,可以对消息有三种处理方式:
- Message对象的callback属性不为空时触发,优先级最高,处理方式是让代表一个Runnable的callback对象run起来,需要注意的是这种方法并不会创建新的线程;
- Message对象的callback为空,Handler对象自己的mCallback对象不为空触发,优先级为中,处理方式为调用传入的mCallback对象的handleMessage方法;
- Message对象的callback为空且Handler对象自己的mCallback对象为空时才有机会触发,优先级最低,处理方式为调用Handler自己的handleMessage方法。还有一种情况是Message对象的callback为空,Handler的mCallback不为空,但是Handler的mCallback对象的handleMessage方法返回false,此时也可以运行到Handler自己的handleMessage方法。
也就是说,虽然Handler的handleMessage空方法的注释上标着由子类实现来接收消息,但是真的接收消息的时候它的处理优先级是最低的2333,而Message的callback不为空时优先级虽然是最高的,但此时由于只是让callback的run方法运行起来,所以传入的Message是没什么卵用的,真正可以对传入的Message做处理的是后两种处理方式,这里应该注意一下。
至此消息处理机制的发送消息(Handler),运送消息(Looper),处理消息(Handler)都详解完毕,算是大概摸出了一个架子,当然完整的消息处理机制不止这些,下面我们来看消息的产生(Message)和消息的排列(MessageQueue)。
Message
消息的产生
事实上,Message的源码并没有将Message的构造方法设为私有,所以需要消息对象时使用一般的new对象的方式是没有错误的:
|
|
但是用这种方法获取Message对象是有很多弊端的,一个庞大的项目是需要很多Message对象进行消息传递的,如果每次都使用new的方式来获取Message对象,对堆内存的消耗是很可怕的,对这种情况,很多优秀的框架都进行了优化,Message类中也做了相应的处理,即使用“池”。
|
|
上面就是官方推荐的获取Message对象的obtain方法,sPoolSync是一个静态的object对象,在这里就是作为obtain方法中代码块的锁对象,同时由于obtain方法和sPoolSync都是静态的,所以这个锁相当于类锁,可以保证不同线程的不同对象访问该代码块时都保持同步。
除了这个无参的obtain方法,Message还有很多可传参的obtain方法,但是本质上还是调用了这个无参的obtain,然后将参数赋值到obtain获取到的message对象的对应属性上再返回。
除此之外,在Handler类中也可以看到一系列获取Message对象的方法,其本质也是调用了Message对应的obtain方法,所以需要使用Message对象的时候,除了new不推荐,使用Handler或者Message的obtain系列方法都是可以的。
回到obtain方法,看到在进到加锁的代码块后,先对sPool进行了一次判空,如果为空就直接返回一个new的message对象,sPool也是一个Message对象,在这里的作用其实是作为储存Message实例的池的链表的头,那么不为空之后的代码也就好理解了,将头部的对象取出,sPool指向链表的下一个对象,池的数量减一,然后将取出的对象返回,这样就实现了实例的复用,避免一直new新对象占用内存。
话是什么说,然而这些代码都是在sPool != null的条件下才运行到的,不然还不是需要new对象返回。那么sPool是在什么地方实例化的呢?
这就需要再回到之前曾经提及过一次的、用于消息的回收的方法了。
消息的回收
Message类对消息回收的处理提供了两个方法:
|
|
很明显,recycle方法最后也是调用了recycleUnchecked方法,只不过在调用前通过isInUse方法进行了一下检查,确保当前message对象的状态不是正在使用才会进行回收。
那么看一下最后调用的recycleUnchecked方法,加锁的代码块之前的代码很好理解,将引用释放,int值都赋值为0,状态位标志为正在使用(放到池中的Message实例都被标志为正在使用的flag),重点是加锁的代码块:
|
|
sPoolSync同样作为锁,与obtain方法中相同,然后判断如果现在的sPoolSize小于定义的最大池容量,就将next指向当前的sPool,然后将sPool指向当前对象,即将当前回收的这个message对象放到了池链表的头部,最后将sPoolSize加一。
如果sPool一开始为null,则在一个Message对象调用回收方法时,sPool就指向了这个Message对象,也就得到了实例化,此时再调用obtain方法,就会将这个回收的Message对象拿出来返回到使用obtain方法的地方。
MessageQueue
作为消息的容器,消息队列对消息的管理和前面Message对实例的管理相同,采用了链表的数据结构,成员变量mMessages就是链表的头。MessageQueue类中包含了Java层和native层两层的操作,但是就消息处理机制来说,很多native层的操作可以不必细究,但是有一个Java层的方法还是要掌握的,那就是负责向消息队列中插入消息的enqueueMessage方法。
这是一个前面提到过的方法,事实上向消息队列中添加消息的操作不是由MessageQueue自己进行的,而是由handler对象进行的,handler调用sendMessage系列或者 post系列方法发送消息时,最终都会调用到enqueueMessage方法,而handler的enqueueMessage方法最后会调用到绑定到该handler的MessageQueue对象的enqueueMessage方法:
|
|
由于方法比较长,这里只留下了关键代码。首先可以看到,由于每个线程都有自己的一个消息队列,所以这里的锁用的是对象锁,一个线程中只有一个消息队列对象的实例存在,所以可以实现同步。
方法中,首先对传入的message对象标记为正在使用,然后将message对象的when成员赋值为传入的when,然后用临时变量p拿到了链表头mMessages的引用。
下面的if-else语句是整个方法的核心:首先,有三种要把当前传入的消息对象放在消息队列头部的情况,一是链表头部引用为空,这说明消息队列中还没有任何消息存在,可以直接将传入的消息对象放在头部(即将代表头部的mMessages指向当前传入的消息对象);二是传入的when为0,这说明这个消息是需要即刻被处理的,而不是需要延迟(delay)处理的消息,所以也要放到消息队列的头部;三是传入的when比当前消息队列头部的message的when要小,这说明传入的消息要在当前消息队列头部的消息之前处理,所以也是要放到队列的第一位成为新的头部。
如果不是上述三种情况,则说明这个消息不是要放到队列头部的,而是需要放到队列中间或最后的某个位置的,所以进入下面的else语句。
else语句功能就很明显了,对队列后面的元素进行遍历,遍历到传入的when小于某个元素的when,则将这个message插入到那个元素的前一个位置;或者遍历到最后一个元素之后(p为null),则将message插入到队列里的最后一个位置。
总结
至此,整个消息处理机制完整的显露了出来,简单回顾一下。
Message作为消息的实体,各种作为数据载体的成员变量不用多说,需要注意的是用于获取实例的obtain方法和回收实例的两个recycle方法,Message内部通过这两个方法维护了一个链表,实现了内存的优化和实例的重复利用,避免了内存的浪费;
MessageQueue作为消息队列,安分守己的做好了一个队列该做的事,维护一个消息链表的同时,提供了一个向队列中插入消息的方法,方法中通过when和链表头将消息插入到合适的地方;
Looper是整个消息处理机制的地基,prepare方法获取某个线程里的唯一实例,实例初始化的时候顺便绑定了线程和初始化好了当前线程的消息队列,loop方法开启了对消息队列的循环,所以说looper的职责就是初始化队列,然后作为一个消息的运输工存在;
Handler就比较忙了,它的初始化方法里拿到了Looper和Looper初始化好的消息队列,然后就一边负责产生消息,一边负责处理消息,它是消息的工厂和处理器。
工厂产生消息,放到消息队列中,运输工按顺序从队列中取出消息放到处理器里处理,各有职责,又相互配合,这就是消息处理机制。