前言
很长的一段时间我一直在使用Handler
,主要是在处理异步任务的时候来实现线程切换,不过对其原理和工作流程并没有详细的做过了解,以下我把从分析源码得到一些内容做出了一些总结。
从源分析Handler/MessageQueue/Looper的工作流程
首先来看下如下的示意图,图中描述的对象之间的基本通信。
首先是Handler
对象发送了一条Message
,然后消息会被存放到一个列表(队列:MessageQueue
)中,紧接着有一个叫做Looper
的对象会不停的去这个队列中寻找有没有新的消息,有的话将消息分配给Handler
对象进行处理(每一个Message
对象都会默认持有一个Handler
对象的引用,这个Handler
对象就是发送这个Message
的对象,在Message
对象内部被定义为target
变量)。其具体的消息会被放在target
所在的线程中执行。接下来详细介绍消息的收发和处理过程。
Handler的创建过程
首先先来看一下Handler的构造函数,如下图,Handler一共向外提供了4个构造函数(其实在内部一共提供了是7个构造函数,只不过对外是隐藏的)。
1 | //Step1 |
可以看到的是我们在调用无参数构造方法时其实的调用的内部的具有两个参数的构造方法,第一个要求传入一个回调实现(后便会具体介绍),第二个则是否异步的标识符。
在Step2
中可以看到几个比较关键的内容,第一个关键点就是在我们新建的Handler对象内部保存了一个Looper对象的引用,这个Looper.myLooper()
函数获取的是当前线程所持有的Looper对象(线程中默认是没有Looper对象的,只有调用Looper.propare()
函数之后才会在当前线程中创建一个唯一的Looper
对象,所以如果没有则会抛出一个异常,这个异常就是我们最初在子线程中使用Handler提示的异常信息。);第二个关键点则是从Looper对象中拿到了一个消息队列对象mQueue
,这个对象是一个MessageQueue,它是在Looper被创建时创建的。
Looper/MessageQueue的创建过程/时机
MessageQueue
是跟随Looper
的创建而创建的,在一个线程中只会存在一个Looper
对象,也就是说在一个线程中MessageQueue
也只会存在一个(理论上来说)。下面从源码中来印证如上所说。
1、来看Looper.prepare()
函数
这个方法仅仅是提供了一个入口方法,实际上调用的是内部的另一个prepare
方法。紧接着内部的这个prepare
方法通过new
的方式创建了一个Looper
对象,也就是在Step3
的内容。可以清楚的看到这里为Looper
的内部变量mQueue
进行了赋值,也就是在这个时候MessageQueue
被创建。
在Step2的时候我们发现调用了一个叫做sThreadLocal
的变量的set
函数,这个ThreadLocal
并非是一个线程,它是用来存储线程中数据的,具体可参考我的另一篇文章:ThreadLocal是什么?
1 | //Step1 |
在Looper对象中有一个非常重要的函数,那就是loop
了,中文翻译过来就是循环的意思,这个函数会帮助我们不停的从MessageQueue
中来获取Message
。
2、来看MessageQueue
目前来说,MessageQueue
的创建并没有什么值得我们关注的,它只是提供了一个先进先出的机制来帮助我们存取消息,但是我们需要知道它所提供的两个非常重要的方法,第一个就是enqueueMessage
,这个函数是用于将Message
对象添加到队列中的,第二个就是next
函数,该函数是从消息队列中取消息的,取出来后会立刻从队列中移除。
Handler的消息发送过程
如下是一个基本的消息发送流程,基本使用这里不在赘述。
1 | Handler handler = new Handler(){ |
1、走进handler.sendMessage(Message msg)
函数中来一探究竟。
1 | //Step1 |
如上可以看到的是在我们调用handler.sendMessage(Message msg)
函数时,它会调用内部的sendMessageDelayed
函数,这个函数是用于发送定时消息的,因为sendMessage
发送的都是需要立即被处理的消息,所以传入的就是0了,紧接着sendMessageDelayed
函数又调用了sendMessageAtTime
函数。
在这个sendMessageAtTime
函数中我们需要关注的是enqueueMessage
的调用,这个enqueueMessage
函数是帮助我们把消息加入到MessageQueue
对象中。在如上Hanlder
创建过程的描述中,我们说了:这个消息队列(mQueue
对象)是在Handler
创建时从Looper
对象中获取并保存到局部变量中的。
2、来看Handler中的enqueueMessage
函数
1 | private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { |
这里有两点我们需要特别关注,第一个就是把当前Handler
对象的引用给了msg
的target
变量,这其实就是为之后的消息处理提供处理者,在加入到消息队列之前会默认把Message
的target
设置为发送Message
的Handler
对象,即便我们在创建Message
对象时设置了target
也会在enqueueMessage
函数中被重置,由此可以得出,Message
对象的发送者即是Message
的处理者。
到这一步消息已经被添加到了MessageQueue
对象中,至此Handler
的sendMessage
任务就算完成了,也就是说它成功的将消息递交给了MessageQueue
对象。
Message的处理过程
在上一段的结尾我们知道了Handler
的sendMessage
函数会把我们的Message
对象加入到一个叫做MessageQueue
的对象中,也就是说我们只是把消息保存了起来,单纯的消息保存没有任何意义,所以引入了Looper
对象来不停的从MessageQueue
中拿数据并且交给消息的target
对象来进行处理。
1、来看Looper
的loop
函数
1 | public static void loop() { |
以上就是Looper
的loop
函数了,为了便于观看,这里我删减掉了无关的代码,并标注了4条较为重要的代码。
第一步:调用Looper
对象内部的myLooper()
函数,这个函数是从ThreadLocal
对象中取出当前所在线程的Looper
对象,它是我们在创建Looper
时保存的Looper
对象,也就是我们在上边介绍Looper
创建时看到的sThreadLocal.set(new Looper(quitAllowed));
。
第二步:拿到我们当前线程中持有的MessageQueue
对象,在上边我们说了MessageQueue
是随着Looper
的创建而被创建的。也就是说我们拿到的Looper
和MessageQueue
都是当前线程中的。至此你应该要知道Looper
和MessageQueue
在每一个线程中都是可以存在的,但是更要知道的是:在每一个线程中有且只有一个Looper
和MessageQueue
对象。如下我绘制了一张图帮助更好记忆和理解。
第三步:从MessageQueue
中取出我们使用Handler.sendMessage
存放进取的消息。
第四部:这一步其实是最核心的一部了,它通过调用Message
对象中的target
变量的dispatchMessage
函数,将消息交给target
对象进行处理,在上边我们说了target
对象就是发送Message
对象的Handler
。所以最终的消息处理会被放在该对象中被处理。
2、来看Handler
的dispatchMessage
函数
如下就是我们在上一步看到的loop
函数中调用的dispatchMessage
函数。
1 | public void dispatchMessage(Message msg) { |
首先它会判断Message
的callback
变量是否为NULL(在Message.obtain()
函数中可以传入callback
),如果存在callback
那么会优先处理Message
的callback
,否则会继续判断当前Handler
对象的callback
是否为NULL(这个callback
是在构造Handler对象时是可选传入的),如果还不行那么就调用Handler
中的handleMessage
函数,这也是我们常见的消息处理方式,也就是我们在上边重写的handleMessage
函数。
这里我们需要知道的就是消息处理的优先级:
1、由Message
对象的callback
处理(这个callback
是一个Runnable
对象)
2、由Handler
对象的mCallback
处理(这个callback
是Handler
对象中的一个接口提供了一个用于消息处理的回调public boolean handleMessage(Message msg);
)
3、由Handler
的handleMessage
处理(这个就是Handler
对象中的一个方法了,只有默认实现没有任何代码,通常需要重写)
另外需要知道的是:dispatchMessage
函数中所有的消息都是在Handler
对象所处的线程中被执行的。
消息的发送和处理总结
1、调用Looper.prepare
函数
帮助我们创建Looper
对象和MessageQueue
对象。
1 | private static void prepare(boolean quitAllowed) { |
2、创建Handler
对象
在这一步主要是拿到当前线程的Looper
对象以及Looper
对象中的MessageQueue
对象并保存其引用。
1 | public Handler(Callback callback, boolean async) { |
3、调用Handler
的sendMessage
函数
该函数最终会走到Handler
对象中的enqueueMessage
中,将消息保存到当前线程的MessageQueue
对象中。
1 | private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { |
3、Looper
对象的loop
函数
当前线程的Looper
对象不断的从它内部的MessageQueue
对象中取消息,然后交给Message
的target
来做处理。
1 | public static void loop() { |
4、Handler处理消息
到这里会根据优先级来处理消息,且消息的执行是在当前Handler
所在的线程中。
1 | public void dispatchMessage(Message msg) { |
至此核心的处理流程及已经完成了。
主线程中不用手动创建Looper的原因
Android主线程即ActivityThread
,在主线程的入口方法main
方法中调用了Looper的prepareMainLooper
函数,该函数是专门为主线程提供创建Looper
使用的。
public static void main(String[] args) {
//......省略无用代码
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
总结
持有关系:
1、一个线程中只有一个Looper对象和一个MessageQueue
2、一个线程中可以有多个Handler对象
3、MessageQueue是包含在Looper中的
注意点:
1、Handler必须在持有Looper的线程中才能创建。
2、Handler的回调优先级(1、Message.callback2、Handler.callback、3、Handler.handleMessage)。
3、在使用Handler发送Message时,Message的target会被默认设置为Message的发送者。
最后
Handler
、Message
、MessageQueue
、Looper
组成了Android强大的消息机制,以上只是简述了其中的部分内容,还有很多的知识点等待日后进行挖掘。
原创文章,转载请标明来源。