我们都知道在UI线程中不能进行耗时操作,例如数据读写、网络请求。Android 4.0开始,在主线程中进行网络请求甚至会抛出Android.os.NetworkOnMainThreadException。这个时候,我们就会开始依赖Handler。
后来随着AsyncTask、EventBus、Volley以及Retrofit 的出现,Handler的作用似乎被弱化,逐渐被大家遗忘。其实不然,AsyncTask其实是基于Handler进行了非常巧妙的封装,Handler的使用依然是其核心。Volley同样也是使用到了Handler。因此,我们有必要了解一下Handler的实现机制。
Handler的工作原理是Handler发送消息给MessageQuee给形成Handle MessageQuee再由Handle MessageQuee传递消息给Looper(一个对象)最后由Looper递交给 Handler处理(一个Handler里面必须要有一个Looper)
Handler、Looper、MessageQueen、Message的关系
Message: Handler接收和处理的消息对象
MessageQueen: 存储消息对象的队列
Looper: 负责读取MessageQueen中的消息,读到消息之后就把消息交给Handler去处理。
Handler:发送消息和处理消息
首先看下Handler如何与当前线程绑定?我们看下Handlerd的构造方法:
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
这是有所删减的构造方法,保留重点。看第二行和第七行,这都和Looper有关,而Handler和线程的绑定就是通过Looper。在这里mLooper = Looper.myLooper();是获得当前线程相关的Looper(因为是构造方法,所以就是创建Handler线程相关的Looper), mQueue = mLooper.mQueue;是获得该Looper对象的消息队列(这又说明了Looper对象持有消息队列的引用),因为Looper和线程关联,所以消息队列和线程关联。
这样一看,是不是觉得天晴朗了许多?Looper和当前线程关联,消息队列又在Looper中,Handler一旦得到了对应线程的Looper引用,也就和当前线程和消息队列关联上了,不是么?但是,Looper本身又是如何关联上当前线程的?
点进去看mLooper = Looper.myLooper();的源码:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
就一行,这个ThreadLocal对象又是什么鬼?
之前官方说创建一个线程的Looper使用Looper.prepare(),点进去看一下:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
要获取当前线程相关的Looper是在ThreadLocal对象取出来的,而要创建一个前线程相关的Looper则是通过ThreadLocal对象set出来的(注意到异常信息,说明一个线程关联的Looper只有一个),是不是隐隐约约觉得Looper和当前线程关联的阴谋就隐藏在这个ThreadLocal类中?
关于他我看了几篇博客,很大一部分写的是错的,所以
注意:
目的:他只是为了保证每个线程都拥有同一个类的不同对象
实质:每个线程里都new了同一个类的对象
作用:你或许觉得这样做很傻,但是如果使用全局变量就要考虑线程安全问题,而线程安全是以消耗性能为前提的,所以这种设计可以说很巧妙
场景:每个线程都需要相同类型的对象,又各自独立,并且与他相关的操作不少时,如,Looper,ActivityThread,AMS 等
我们看一下ThreadLocal的源码,主要分析get()和set()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
所以过程就很清晰了,我们来总结一下:
看起来不好懂。首先必须明确一点,一个线程只能一个Looper对象,从而只有一个MessageQueue(在Looper的构造方法初始化),从上面代码当ThreadLocal对象get方法返回不为空则抛出的异常信息可以看出。大概就是所有的线程共享同一个ThreadLocal对象,不同的线程在该对象存储某个变量的值是不同的。比如我们的Looper的prepare()中,当在不同的线程中ThreadLocal对象将新创建的Looper对象set进去的时候,不同线程get出来的Looper都是各自set进去的Looper对象。所以mLooper = Looper.myLooper();的意思就是“把我现在所在的线程的Looper取出来给我吧”的意思(至于ThreadLocal如何实现这看起来很神奇的功能,是因为它内部为不同的线程维护一个数组,每次存取数据都是从对应线程的数组操作的)
所以,Handler关联创建自己对象的线程的过程就是这样子~
那Handler对象是如何将消息发送到绑定的消息队列(即创建Handler对象的线程关联的消息队列)呢?
大家都知道Handler发送消息的方法是sendMessage(),看源码:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
再看下sendMessageDelayed的源码:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
调用了sendMesaageAtTime()再进去:
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);
}
重点是enqueueMessage方法,而这个方法会调用消息队列的enqueueMessage方法。前面说过,消息队列实质是一个链表,当handleMessage被调用之后,Message对象会被插入链表,具体源码如下:
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) {
// 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;
}
因为消息可以设置delay 参数,所以在插入链表前还要判断下延时参数when,会根据延时由短到长按顺序插入。
好了,消息已经插入队列,剩下的工作就是将消息取出来执行。前面说到Looper的消息循环机制,当我们prepare创建了一个当前线程Looper后,调用loop开启消息循环。所以看下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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
抛开抛异常和Log代码,其实很简单。就是进入了死循环。不断从自己的消息队列取出消息然后调用 msg.target.dispatchMessage(msg);(取不到则阻塞),而这个msg.target,就是Message对象持有的指向发送的Handler对象的引用,所以现在是调用了发送该Message的Handler对象的dispatchMessage方法去处理这个Message对象。
看下Handler的dispatchMessage的源码:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
前面几行是判断下Message或者Handler本身是否有设置回调接口(Message的callBack就是Handler所post的Runnable对象),有则调用回调接口去处理Message对象,没有则调用我们熟悉的handleMessage处理Message,至于怎么处理大家都清楚吧。
总结一下:我们在创建Handler对象的线程外其他的线程通过一个Handler对象发送消息(也可以在本线程去post一个任务),将消息(post出去的Runnable也会被封装为Message)发送到创建Handler对象的线程所关联的Looper对象所持有的消息队列中,然后等待正在消息循环过程中的Looper对象去从队列中取出这个Message,一旦取出该Message,则调用发送该Message的Handler对象的handleMessage方法去处理这个Message。
可以举一个通俗点的例子。设想一个场景,教室(线程)里的学生(Handler)有很多,只有一个老师(Looper),学生去不同教室(即不是创建该Handler对象的线程)学习写作业(创建Message),然后将作业带到自己教室里按顺序放到老师的桌子上(sendMessage插入消息队列),老师不断按顺序批改作业,每批改完一份作业就叫对应的学生过来拿作业本去修改错误(handleMessage)。
借鉴了俩大佬的博客
https://blog.csdn.net/sinat_23092639/article/details/52334398
https://blog.csdn.net/qijinglai/article/details/80685226