Handler是Android提供的一种异步回调机制,主要实现消息的线程间通信,那么问题来了,为什么需要Handler?主要是2个原因,1是为了避免耗时操作阻塞主线程(包括网络请求)造成UI卡顿或者ANR,Android系统要求用户把这些个操作放到子线程中,2是Android同时又要求在子线程不能更新UI(估计是基于线程安全问题),这就需要一种机制,在子线程中把消息传递到主线程,于是就有了Handler。
要实现异步回调,单单有Handler是不够的,Android还提供了Looper,Message,MessageQueue,这些类共同实现了异步回调功能。通常情况,我们在Activity直接实例化一个Handler对象mHandler,在子线程中通过mHandler发送消息,然后在实例化Handler时传入的回调方法中接收处理方法,此时消息已经到了主线程上。所以,我们从实例化Handler开始进行深入分析。
1,开始实例化Handler
1,在主线程实例化Handler
private Handler mHandler=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
});
2,进入Handler内部实现,到了最终的构造方法中
public Handler(@Nullable Callback callback, boolean async) {
...
mLooper = Looper.myLooper();//取出保存好的Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//取出Looper中的消息队列
mCallback = callback; //后面处理消息的回调
mAsynchronous = async;
}
3,取出保存好的Looper的过程
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
4,ThreadLocal是什么,起的作用是什么?具体的看下ThreadLocal中set/get()的实现
ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();//当前线程
ThreadLocalMap map = getMap(t);//得到线程中的一个变量,作用类似于HashMap,可以通过k-v保存数据
if (map != null)
map.set(this, value); //map以ThreadLocal为key,以Looper为value
else
createMap(t, value); //创建一个以ThreadLocal为key,以Looper为value的map给thread
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//取出map中的value,也就是looper
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
所以,Looper中的sThreadLocal明着像是一个保存looper的容器,但实际却是自己为key,looper为value,保存在线程Thread中的map中
2,Handler的实例化后,我们看到内部把Looper,MessageQueue取出来,那就有必要知道这些是如何产生的
1,在主线程中取出来的,那肯定是在主线程实例的,而且是在主线程(ActivityThread)刚启动的时候
ActivityThread.java
public static void main(String[] args) {
...
Looper.prepareMainLooper();//重点1
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);//这里之前详细分析过了,主要为了application/activity的创建和启动
...
Looper.loop();//重点2
throw new RuntimeException("Main thread loop unexpectedly exited");//抛异常, 主线程的loop出乎意料的退出了
}
2,重点1,Looper是如何产生的?
Looper.java
public static void prepareMainLooper() {
prepare(false);//关键语句,实例一个Looper,并保存
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();//实例化的Looper赋给sMainLooper
}
}
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));//通过threadLocad保存new出来的Looper
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//取出来
}
3,重点2 Looper是如何工作的?
public static void loop() {
final Looper me = myLooper();
...
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;
}
msg.target.dispatchMessage(msg);
...
}
}
简化以后就很明了了,loop()中取出之前实例好的looper,并取出looper中的消息队列queue,开启死循环,不停的执行消息队列取消息操作,
message.target是私有变量handler
Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//一般常见于使用handler.postXX(Runnable)发送的消息
handleCallback(msg);
} else {
if (mCallback != null) {这是之前实例化Handler传进去的回调监听类
if (mCallback.handleMessage(msg)) {//回调类的处理方法,实际在activity上,这样就把消息队列的消息传到主线程上了
return;
}
}
handleMessage(msg);//如果实例化Handler未传入回调类,则必须重新Handler本身的HandleMessage()方法,就是这个方法,同样的,把消息传到主线程上了
}
}
小结:通过查看Looper的实例化和Looper.loop()的调用,我们清楚的看到了消息的处理流程,
loop()依次从messageQueue中取出message,然后从message中取出handler去分配处理消息。
3,现在知道消息message是从消息队列messagequeue中取出来,那么必定有把消息放到消息队列的的过程
1,通过handler发送message的方法有2类,一种是send->Message系列,一种是post->Runnable系统
方法中带Message参数
sendMessage()//常用
sendMessageDelayed()//延迟接收的消息
sendMessageAtTime()//准确时间接收的消息
sendMessageAtFrontQueue()//见名知义,放到消息队列最前面,也就是马上执行的消息
sendEmptyMessage()//空消息
sendEmptyMessageAtTime()
sendEmptyMessageDelayed()
方法中带Runnable参数,通过getPostMessage()生成Message,runnable参数传入到message中(处理消息时使用此回调)
post()
postDelayed()
postAtTime()
postAtFrontOfQueue()
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
上面所有发送消息的方法,最终都是通过sendMessageAtTime(Message msg, long uptimeMillis)来发送的(方法千千万,最终汇总),uptimeMillis为处理消息的时间
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;//这是Looper中的消息队列
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//this就是当前的handler,处理消息时就是用的msg中的target
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//最终,handler把消息送到了消息队列中,然后消息队列进行入队操作
}
4,击鼓传花,消息终于要传到消息队列去了
0,在消息message进入消息队列messagequeue之前,我们需要对Message和MessageQueue的基本结构有初步的了解,方便了解入队的整个过程
Message:有个when字段,表示当前消息处理的时间,同样的,message关联指向了另一个message,字段名为next,所以,整个Message可以组成一个链表结构
MessageQueue:消息队列管理保存着消息,因为Message可以组成一个链表结构,所以MessageQueue只需要持有一个Message对象就能完成对Message的保存和管理,
因为消息队列有处理时间的要求,所以消息队列持有的头消息必定是最早处理的(when最小),所以在入队的过程就必然需要有对时间的比较。
1,enqueue的就是入队的意思
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.markInUse();
msg.when = 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; //头消息变成msg的next消息,这样就当前消息变成了头消息,原先的头消息变成第二个消息对象
mMessages = msg; //msg赋值给消息对象持有的头消息
needWake = mBlocked; //不需要唤醒,这个变量后面再讨论
} else { //下面的操作是msg要插入到消息链表中间去
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;//临时变量
for (;;) {//死循环遍历消息链表,查找msg需要插入的位置
prev = p;//当前查找的消息放到prev上,第一次当然是消息队列持有的头消息
p = p.next;//取出下一个消息为p 此时 prev->p
if (p == null || when < p.when) {//如果下一个消息为空(到链表尾巴上了) 或者时间戳小于下一个消息的时间戳 则位置找到了
break;//跳出死循环,位置找到了,msg要插在p前面,此时prev->p,执行插入后期望的顺序为 prev->msg->p
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; //
prev.next = msg;//这2步实现的功能就是链表顺序变为 prev->msg->p 此时头消息还是mMessage,但msg已经到它的链表上了
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr); //有消息来了,底层唤醒Looper
}
}
return true;
}
2,这样就很明了了,发送的消息经过比较和插入,终于进入到消息链表中了,结合前面的Looper.loop(),这样,整个流程就清楚了。
总结:大体流程是这样的,在主线程创建的起始阶段(ActivithThread.man()),会创建Looper的同时,Looper构造方法中创建一个消息队列MessageQueue,在main()的后面,调用Looper.loop(),loop()会调用Looper中实例化的Messagequeue,源源不断地通过queue.next()获取Message,然后通过message设置的target(也就是Handler)来调度处理消息,而我们也可以通过实例化好的Handler的一系列send/post的方法,将消息发送到消息队列中。举个现实中栗子:
只要地铁站运行正常,那么过安检的机器必定需要开启运行,地铁站可以类比成ActivityThread,安检机器为Looper,安检设备持续不断的运行(调用Looper.loop()),安检设备有个传输带(MessgaQueue),每一个行李(Message)都必须放到传送带上,放的过程是handler的send/post(),行李经过检测没问题后,可以从另一端取走(handler.dispatchMessage(message)),如果handler.send/post()操作是在另一个线程中执行的,等到消息取出来被调度处理时,已经是在主线程了,这就实现了异步回调。
Handler工作原理图大体如图
这样,Handler的整个工作流程就梳理清楚了。
当然,说到死循环,为什么looper.loop()不会因为死循环造成线程阻塞?
答案很简单,整个App因为主线程中loop.looper()的死循环而保持活动,如果跳出死循环,整个App会抛异常,
ActivityThread.main(){
...
Looper.loop();//重点2
throw new RuntimeException("Main thread loop unexpectedly exited");//抛异常, 主线程的loop出乎意料的退出了 这句话正常情况下不会被执行
}
而且,造成主线程堵塞是因为对Message处理的超时造成的,以之前举例说明,安检设备因为传输带不断循环而保持正常工作,一旦行李过重,用户从传送带上搬离行李过慢才会造成后续行李的卡住(各类事件不被处理,造成阻塞)。
同样的,在消息队列中没有消息时,主线程会进入休眠状态,即消息队列执行next()方式时,会根据下一个消息还有多久执行系统底层方法nativePollOnce()释放资源进入休眠状态
MessageQueue.next(){
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);//直到下条消息到达或者有事务发生,
}
而一旦有最新消息事件进入到消息队列时,一旦新事件需要马上执行或者到了执行时间的时间,会通过系统底层方法nativeWake()唤醒主线程进行工作
MessageQueue.enqueueMessage(){
...
if (needWake) {
nativeWake(mPtr);//通过往 pipe 管道写端写入数据来唤醒主线程工作,这里采用的 epoll 机制
}
}
主线程在没有任务时执行的睡眠机制很大限度的优化了性能,同样的,在发送消息时,使用obtain比直接实例更有效率。
1,Message.java
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*从全局池中返回一个新消息实例
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;//Message类内部也有一个消息头,相当于也有一个消息链表 头消息置于m
sPool = m.next; //下一个消息置为表头
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;//
return m;//返回当初的表头消息
}
}
return new Message();
}
这样的话也不用每次都实例消息,最大限度的提高效率。
总结:Handler作为消息回调机制因为涉及的关联类比较少,整体结构是相对比较清晰的,但内部的逻辑还是很有学习价值的。