首先列出几个Handler 相关的问题,如果你都知道答案,就可以不用跳过这篇文章。当然,如果文章对你有帮助,还希望给个小小的赞,鼓励下。
说明,源码分析基于andoird 9.0
- Looper 使用了死循环,为什么没有阻塞线程?
- 子线程中能不能直接new一个Handler,为什么主线程可以?
- 如何在子线程中使用Handler?
- 一个线程可以有多少个handler,多少个looper,多少个MessageQueue?
- MessageQueue的数据结构是怎样的,当发送延迟消息的时候,MessageQueue有什么变化?
- 一个线程创建了多个多个handler 时,looper 如何给到指定的handler 处理消息
- handler 如何进行线程切换的
第一个问题
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
// Looper.loop();
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block //可能发生阻塞,但是不会阻塞线程
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
}
再看下queue.next();
private native void nativePollOnce(long ptr, int timeoutMillis);
Message next() {
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); //调用ndk方法
#省略多余代码
}
}
关于第一点为什么for循环没有阻塞主线程,是因为Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。
子线程中能不能直接new一个Handler,为什么主线程可以? 如果在子线程中直接new Handler(),会发生什么呢,看源码
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//再看下Looper.myLooper();
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get(); //如果没有调用Looper.prepare()的话,此处会为null
}
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread"); //如果多次prepare会抛异常
}
sThreadLocal.set(new Looper(quitAllowed));
}
所以第二个问题的原因就是直接new Handler()时,由于没有准备looper 所以会抛异常,那为什么主线程直接new Handler()不会抛异常呢,是因为在ActivityThread的main方法中就已经创建了主线程的looper
Looper.prepareMainLooper();
第三个问题在子线程中使用Handler,由第二点我们可以知道第一步需要准备子线程的Looper,然后才能new 子线程的Handler,最后开启Looper.loop();循环取MessageQueue中的消息就可以了
首先准备好Looper
Looper.prepare();
val handler=Handler ();
Looper.loop();
这样就能在子线程中使用Handler
第四个问题:一个线程可以有多少个handler,多少个looper,多少个MessageQueue? 我们知道android 系统中已经存在主线程的handler,来分发Activity、fraggment的生命周期调度,我们也有在主线程中new Handler(),来做一些操作,比如果hanlder做线程切换。所以一个线程中,肯定不止一个Handler. 由下面的源码分析,一个线程,Looper也只能有一个。否则会抛异常
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread"); //如果多次prepare会抛异常
}
sThreadLocal.set(new Looper(quitAllowed));
}
并且由于MessageQueue是在Looper中初始化的,所以MessageQueue也只能有一个
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
第四个问题,还可以延伸出,如何保证Handler怎么做到的一个线程对应一个Looper? 由上面Looper.prepare()可知,looper 是被保存在ThreadLocal中
什么是ThreadLocal? 设计的初衷是为了解决多线程编程中的资源共享问题, synchronized采取的是“以时间换空间”的策略,本质上是对关键资源上锁,让大家排队操作。 而ThreadLocal采取的是“以空间换时间”的思路, 它一个线程内部的数据存储类,通过它可以在制定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据, 对于其他线程就获取不到数据,可以保证本线程任何时间操纵的都是同一个对象。比如对于Handler,它要获取当前线程的Looper,很显然Looper的作用域就是线程,并且不同线程具有不同的Looper。 ThreadLocal本质是操作线程中ThreadLocalMap来实现本地线程变量的存储的 ThreadLocalMap是采用数组的方式来存储数据,其中key(弱引用)指向当前ThreadLocal对象,value为设的值 通过ThreadLocal计算出Hash key,通过这个ThreadLocal对象,value为设的值
MessageQueue的数据结构是怎样的,当发送延迟消息的时候,MessageQueue有什么变化
拿到问题,我们第一步想下入口是哪里入口,我们可以从handler.sendMessage()方法进行分析
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); // 如果是延迟消息,都会加上系统时间,也就是最后需要发送消息的时间
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis); //到了这里就是消息加入的方法了,继续往下看
}
//最终会调用MessageQueue中的enqueueMessage
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
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) { //这里的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) { //找到最后一个为null,或者当前when小于遍历的message就退出
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;
}
从上面分析可以知道MessageQueue 其实不是一个队列,只是根据时间顺序组成的链表结构。 而发送延迟消息的时候,如果没有消息,则直接放入到队列,如果队列有消息,则去遍历,根据显示时间,插入该位置
关于第六点,可以看源码,handler 下的enqueueMessage,会将Massge与Handler绑定。 msg.targe=this;
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Looper.class
public static void loop() {
#省略多余代码
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg); //最终调用handleMessage() ,这里就不贴了
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
}
}
最后一个问题,我们知道Handler经常被用来做子线程切换到主线程,然后进行Ui操作。那他是如何做操作的呢,其实很简单, 子线程通过使用主线程的Handler,发送Message 到MessageQueue中,Handler是主线程的,那发送的消息,最终处理也肯定是在主线程上,这样就达到了线程切换的目的。
总结: Handler 机制(Handler ,Message,MessageQueue,Looper)通过hander 发送消息,然后给到messageQueue。MessageQueue是一个单链表的存储结构,每发送一条消息会加入队列中。 Looper通过looper的 死循环,从MessageQueue中获取消息,并最终通过handler 的handleMessage方法进行处理
最后附上一张Handler机制的图: