Handler-Message的流程分析,及常见问题讨论
一、handler-message的使用场景:
现在要求在一个子线程里面发送一个消息到主线程去跟新UI的数据,使用handler-message来实现这个问题:
在子线程里面创建了一个message,给message分配了一个tag,作为标记,区别不同的message,
将message封装在msg.obj中,然后通过handler.sendMessage给发送给对应的Handler执行handlerMessage 来接收消息改变UI
二、handler-message的源码分析
handler-message中各个重要的类:
1: Handler: 主线程与子线程的消息媒介, 负责发送和处理消息
2: Looper: 消息队列和handler的通讯媒介, 负责线程的关联和消息的分发,在该线程中获取到messageQueue里面的message,并且分发给handler.
3: Message: 线程中通讯的数据单元, final修饰,实现Parcelable接口, 可用于进程间的数据传递.
4: MessageQueue: 是个队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message。
handler机制的流程:
在主线程里面创建 消息处理器Looper, 消息队列MessageQueue, Handler对象.
1: 主线程之中, Looper的创建已经在ActivityThread中的main方法中默认初始化了.
执行了Looper.prepareMainLooper() 和 Looper.loop()
2: 在prepareMainLooper()执行了Looper.prepare()方法,prepare()里面创建了一个Looper, 获取当前的线程并且初始化了MessageQueue, 然后将这个looper放在ThreadLocal中去.(此时ThreadLocal是有Looper的哈)
紧接着执行looper.looper(),开启无线循环来进行消息的分发.
开启死循环,不断的从消息队列中取出消息,然后对消息进行分发.
问题: 为什么Looper的死循环没造成UI线程卡死.
喔嚯: * 在looper.looper()之后不再执行其余的操作, 然后将异常抛出去. 所以死循环可以保证UI线程不会被退出, Android界面绘制都是通过Handler消息来实现的,这样可以让界面保持可绘制的状态*
handler的构造函数
既然在ActivityThread启动起来了,我们准备好了Looper, MessageQueue,并且创建了主线程的Handle, Handler, Looper, MessageQueue的关系形成了1:1:1
在默认的构造函数里面, handler是没有传looper的,这是因为looper和messageQueue在ActivityThread的main()函数里面是已经被初始化了的, 在这个构造函数里面通过Looper.myLooper方法去通过get()在ThreadLocal里面拿到对应的looper, 进而进行赋值绑定, 此时handler就跟looper以及messageQueue进行绑定了的.
上图是默认构造,传参里面是没有looper的
没有looper的情况使用Looper.looper来绑定当前线程, 那么看看他是怎么绑定当前线程的:
如上图所示:可以看到该方法通过ThreadLocal来拿到当前线程绑定的Looper. (此举就是handler拿到当前线程的looper和messageQueue的引用)
Looper 取出消息,如何发到制定的Handler上面去
前面见到了在创建整个应用的时候,会执行looper.loop(), 形成死循环,不断读取messageQueue的消息.
Message msg = queue.next(); 循环读取
msg.target.dispatchMessage(msg); msg.target = hander =>使用对应的handler进行分发
Message的发送过程:
在发送消息的时候,我们需要使用handler.obtainMessage 或者 Message.obtain 出来一个message, 在message里面存载一些数据, 然后通过handler发送出去.
1: handler发送数据的方式:
sendMessage: 发送一个消息
sendEmptyMessage: 发送一个空的消息
sendEmptyMessageDelayed: 延迟发送空消息
sendEmptyMessageAtTime: 某个时间发送一个空消息
sendMessageDelayed: 延迟发送一个消息
sendMessageAtTime: 某个时间发送一个消息
sendMessageAtFrontOfQueue:发送一个消息到队列之前
2: 我们来跟踪一下sendMessage的源码, 看他是做了哪些操作:
根据代码,我们看到了调用到了enqueueMessage这个方法里面来了:
可以看到在这个方法里面message 的target绑定了当前的handler, 然后将这个消息发送到消息队列里面去了, 那么此时消息队列的message就不为null了,looper就看似读取messageQueue,取出里面的消息,发送给对应的handler进行处理.
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
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();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
重点看这个if - else里面的代码:
1 p是当前的messageQueue的首个message, 如果当前队列没有其他需要发送的message, 或者当前新添加进来的Message的时间节点为0(代表需要立即发送的消息),
或者当前新添加进来的的Message需要发送的时间点小与当前MessageQueue队列头部Message的时间点(即当前添加的message需要在当前的messageQueue队列的头部message之前被发送), 就会进入到if代码块中, 即现在要做的就是吧当前的message插入到队列第一个.
msg.next = p; mMessages = msg; needWake = mBlocked
这就是一个插入的动作
2 messageQueue内部是按照发送时间从小到大排列的,当队首的message未达到发送的时间点时候, 线程会被阻塞. 所以这里需要根据线程是否注射来判定是否需要唤醒线程, 则有唤醒了线程才能及时的把要发送出去的消息发送出去. 唤醒线程是nativeWake()去唤醒,只有线程在唤醒状态才能把消息发送出去.
3: 说明了当前添加进来的Message是在当前MessqgeQueue队首的Message之后才会被发送的,上边分析if部分代码的时候说过了Message是按需要发送的时间先后排列在MessageQueue中的,这里的for循环实际操作就是找到MessageQueue中比当前添加进来的Message需要发送的时间点大的位置,将Message插入到其前边(实际就是一个链表的插入操作)。
handleMessage 接收并且处理消息
当handler把消息发送到对应的handler进行处理的时候,需要重写handleMessage的方法, 这个方法里面是怎么拿到消息的呢?
1 我们回到Looper.loop()中去, 这里创立了一个死循环, 不断的从messageQueue里面去拿消息.然后将消息发送到对应的target 也就是handler去
2 调用dispatchMessage 进行消息的处理, 也就是handlerMessage.
3 那么这个handleMessage是不是很熟悉, 对, 没错,就是我们经常重写的Handler里面的回调方法, 在handler里面handlerMessage是个空实现,等着用户进行重写对消息进行自定义的处理.
整个过程的流程图: (主线程的handler, 实质上在子线程里面handler也是同一个道理和流程,只是ActivityThread把我们需要做的事情已经做了的)
如何在子线程里面创建handler,主线程发送消息,子线程接收消息
请看下面的demo:
开启了一个子线程,在子线程里面处理消息.
1: 创建一个looper
2: 重写里面的方法
3: 循环读取messageQueu
问: 此处的looper可不可以省略, 他的主要的作用是什么? 为什么要这么写? 出现的一些常见的问题:
1: 在子线程中没有默认创建Looper, 在执行handler的myLooper()的时候, 在ThreadLocal里面去获取对应的looper, 但是在子线程里面并没有创建looper.
2: 所以在创建Handler之前,执行Looper.prepare(), 看看prepare()里面的实现.
如果looper已经创建了不等于null, 那么就直接抛出异常. 这说明了: 每个线程只能创建一个Looper, 使用这个Looper来管理对应线程的messageQueue.
3: Handler messageQueue Looper之间的对应关系:
handler跟其余几个都没有对应的关系, 线程可以创建多个Handler, 一个线程对应了一个looper和messageQueue.