好久没写笔记了,是时候来一发啦。
- 说起Handler,相信做Android的朋友都应该对它很熟悉,它的本质为Android下的线程间通信的工具。
- 首先,为什么要有Handler?
- 我们知道,Handler为线程间的通信工具,为什么线程间需要通信,这是因为在Android系统中,主线程(UI线程)是不能做耗时操作的,则进行耗时操作就要放在子线程中,则子线程做完耗时操作后需要通知主线程我完成了耗时操作,那怎么通知呢,当然就是通过Handler,所以说Handler是线程间通信的工具。
- 其次,如何使用Handler?
- 使用Hnadler,这里举例为主线程与子线程通信。首先在需要new 一个Handler对象,重写 handleMessage(Message msg)方法,在该方法中接收到子线程发送来的消息。而开启子线程进行的耗时操作,使用Message对象将需要发送的消息通过Handler发送出去(代码如下)。
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
//接收子线程发送来的消息
}
};
new Thread(){//开启子线程
@Override
public void run() {
//作为耗时操作,使用Handler发送消息
Message message=Message.obtain();
handler.sendMessage(message);
}
}.start();
- 通信的流程:
子线程发送消息到消息队列(MessageQueue),启动Activity的时候已经自动开启了轮循器,执行的Looper.prepare()和Looper.loop()方法,loop方法执行从消息队列中获取消息,又给到handleMessage方法中进行处理,实现通信。
![](https://img.haomeiwen.com/i2174557/428118b9f87bb9d3.png)
相信很多朋友都知道上面所述的流程,但是作为程序猿,只了解表面的操作我认为还是不够的,如果我们能通过解读源码,如果能了解Handler机制的实现原理,这也是提高我们自身的一个过程。接下来,我们就通过阅读Handler的源码来一探究竟。
- 首先查看Handler源码,要发消息,肯定需要有消息,而获取的消息对象是通过Message.obtain()来获取,而Handler源码中的obtainMessage()方法不管是有参还是无参方法也都是调用了Message.obtain();
![](https://img.haomeiwen.com/i2174557/41900639ca964ff2.png)
既然是调用Message.obtain(),我们就去Message类中的obtain()方法,而查看Message中的obtain()方法发现,不管是该方法有多少个参数,最终都是生成的Message对象都是调用无参的构造方法obtain();
![](https://img.haomeiwen.com/i2174557/aa875bed57497fd1.png)
![](https://img.haomeiwen.com/i2174557/ac13c43ca9a05dde.png)
所以,最终创建消息的方法为Message中的无参的obtain方法,
![](https://img.haomeiwen.com/i2174557/ef6c61004a6b685a.png)
该方法的思路为,保存消息的队列的顺序是使用单链表来维护的,如果消息池里面的第一条消息(sPool)不为空,取出该条消息的数据,然后把第二条消息变成第一条;如果没有,新创建一个;这时候就得到了一个消息。
- 得到消息,只是第一步,怎么发消息呢,当然是使用Handler来发消息,我们继续看Handler的源码,而Handler不能凭空使用,是需要new出来的,所以我们看Handler的构造方法,而无参构造方法指向有两个参数的构造方法没,所以我们看有两个参数的构造方法;
![](https://img.haomeiwen.com/i2174557/0297ec07e0b98f5b.png)
又图中的红框,我们可以看到,在new Handler()的时候直接得到了Looper对象mLooper,我们知道,每一个线程对应一个Looper,如果是在子线程中new Hnadler(),不创建传入Looper对象,如图中所示肯定会报异常,而我们一般new Handler()都是在主线程中new,而主线程是在什么时候传入的Looper对象呢,接下来我们继续看源码找答案,先看看是如何获取,
![](https://img.haomeiwen.com/i2174557/74060b18384425d5.png)
看图中源码,是通过ThreadLocal这个类型的数据来获取的,而ThreadLocal是Java提供给我们的类,他的功能为实现线程中的单例,所以才有我们说的一个线程对应一个Looper,在一个线程中无论你什么时候获取Looper对象都是同一个,不会变;
既然这里是get(),则肯定是又set()的方法,而平时要得到轮循器,一般都是Looper.prepare()方法,我们来看看prepare()方法;
![](https://img.haomeiwen.com/i2174557/b4b673eab6a1aaa7.png)
果然,我们看到了set()方法;回到上面的思路,在主线程中是如何传入Looper对象的呢,这时候我们看到Looper中的prepareMainLooper()方法;
![](https://img.haomeiwen.com/i2174557/8e2f3639a08baaba.png)
在看Lopper的方法体,可以看出Looper对象和消息队列都是直接new出来的;
![](https://img.haomeiwen.com/i2174557/31376c802a69b55c.png)
到这里,Looper对象的来源我们搞清楚了,但是在主线程中new Handler()我们在什么时候调用调用上面提到的prepareMainLooper()方法呢,要找到这个入口我们就需要查看ActivityThread.java的main()方法;
public static void main(String[] args) {
...
//创建Looper和MessageQueue
Looper.prepareMainLooper();
...
//轮询器开始轮询
Looper.loop();
...
}
我们可以看到,主线程的入口中保存了主线程的Looper对象和创建了消息队列,接下来开始Looper.loop()方法,该方法是一个死循环,不断的取消息,如果消息队列中没有消息则阻塞,这也就解释了为什么我们Android应用在不进行任何操作的时候也不会挂掉。
Looper.loop()方法的死循环:
while (true) {
//取出消息队列的消息,可能会阻塞
Message msg = queue.next(); // might block
...
//解析消息,分发消息
msg.target.dispatchMessage(msg);
...
}
Linux的一个进程间通信机制:管道(pipe)。原理:在内存中有一个特殊的文件,这个文件有两个句柄(引用),一个是读取句柄,一个是写入句柄;主线程Looper从消息队列读取消息,当读完所有消息时,进入睡眠,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。
- 说了这么多,我们还没开始发消息。好吧,接下来开始发送消息,继续看Handler源码,发消息为Handler中的sendMessage()方法,但是看了几个发消息的方法,发现最后他们掉的还是sendMessageAtTime()方法,所以我们看这个方法就可以了,这时候做了两件事,把handler保存到了Message对象中,而Message对象又保存到了MessageQueue中;
![](https://img.haomeiwen.com/i2174557/b91fb757840b0bb1.png)
![](https://img.haomeiwen.com/i2174557/690020134ba34c9e.png)
而核心的还是如何插入消息enqueueMessage()方法;
final boolean enqueueMessage(Message msg, long when) {
...
final boolean needWake;
synchronized (this) {
...
//对消息的重新排序,通过判断消息队列里是否有消息以及消息的时间对比
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
// 如果消息队列中没有消息,或者当前消息的时候比队列中的消息的时间小,则让当前消息成为队列中的第一条消息
msg.next = p;
mMessages = msg;
needWake = mBlocked; // new head, might need to wake up
} else {
// 代码能进入到这里说明消息队列中有消息,且队列中的消息时间比当前消息时间小,说明当前消息不能做为队列中的第一条消息
Message prev = null; // 当前消息要插入到这个prev消息的后面
// 这个while循环用于找出当前消息(msg)应该插入到消息列表中的哪个消息的后面(应该插入到prev这条消息的后面)
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
}
}
//唤醒主线程
if (needWake) {
nativeWake(mPtr);
}
return true;
}
具体思路为: 对消息的重新排序,通过判断消息队列里是否有消息以及消息的时间对比,// 如果消息队列中没有消息,或者当前消息的时候比队列中的消息的时间小,则让当前消息成为队列中的第一条消息;如果消息队列中有消息,且队列中的消息时间比当前消息时间小,说明当前消息不能做为队列中的第一条消息,则通过比较,找到合适的位置插入消息;而如果子线程发送的消息设置了延时,主线程是如何知道的呢,这时候我们看到获取消息队列的消息的这句话Message msg = queue.next();而next方法中执行就是如何取消息的逻辑,所以查看MessageQueue的next()方法源码;
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration 迭代时间
int nextPollTimeoutMillis = 0;//阻塞时间
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//执行阻塞操作
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) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//时间没还到,设置需要下一轮需要等待的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.得到消息
mBlocked = false;//是否唤醒线程
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.没有更多的消息
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
// 没有idle handlers 在运行,loop需要等待
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
// 重置idle handler 的数量为0,这样我们就不会再次运行
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
// 当调用一个空闲的handler时, 一个新的消息可以被分发
// 因此可以不需要等待,直接查询pending message
nextPollTimeoutMillis = 0;
}
}
通过该方法,所以当我们sendEmptyMessageDelayed发送了消息A,延时为1000ms,这时消息进入队列,触发了nativePollOnce,Looper阻塞,等待下一个消息,或者是Delayed时间结束,自动唤醒; 在当前的前提下我们又发送一个sendEmptyMessage消息B,消息进入队列,但这时A的阻塞时间还没有到,于是把B插入到A的前面,然后调用nativeWake()方法唤醒线程 ,唤醒之后,会重新都取队列,这是B在A前面,有不需要等待,于是直接返回给Looper ,Looper处理完该消息后,会再次调用next()方法,如果发现now大于msg.when则返回A消息,否则计算下一次该等待的时间,也就实现了延时消息处理(@九九叔,谢谢你的提问,这方面问题前面写的时候没说得够清楚)。
- 在消息队列中插入好了消息,Looper.loop方法中,获取消息,然后分发消息;
//获取消息队列的消息
Message msg = queue.next(); // might block
...
//分发消息,消息由哪个handler对象创建,则由它分发,并由它的handlerMessage处理
msg.target.dispatchMessage(msg);
Message对象的target属性,用于记录该消息由哪个Handler创建,在obtain方法中赋值,找到Handler中的分发消息的方法,
![](https://img.haomeiwen.com/i2174557/50ca89ed0540ed26.png)
Handler中的Callback接口,可通过构造方法或其它方法传入,Message中保存了callback(Runnabe)和target(Handler),也可以调用Message的sendToTarget()方法来发消息,前提是必须已经给Message设置了target对象;而最终handleMeassage()的空实现,只要我们new Handler()的时候覆盖这个方法,系统就会执行我们覆盖的handleMeassage()方法,我们就可以在其中收到消息执行我们需要操作。
最后
通过对Handler源码的分析,一步一步的走下来,我相信我们能够对Android线程间的通信机制Handler有一个全面深入的了解。学习是一个积累的过程,如果有写得不好的地方或者有错,请大家给我指出,大家一起讨论,共同进步。
网友评论