Handler的机制原理
基本元素:
Handler:在哪个线程创建,就跟哪个线程的Looper关联,也可以在构造方法传入指定Looper;
Looper:一个线程拥有一个looper,通过looper.loop方法轮询MessageQueue;
MessageQueue:单链表,根据时间优先级排列,负责获取和分发message;
Message:消息对象,被释放后放进 MessagePool , PoolSize < 50,msg.obtain后PoolSize--;
原理:
1)Handler.sendMessage()把消息放到MessageQueue;
2)MessageQueue.enqueueMessage()进行时间优先级排列;
3)Looper.loop轮询MessageQueue,通过queue.next()获得消息。
4)若 queue.next() == null ,会调用nativePollOnce方法进入阻塞状态,只有当 有消息进入 或 quit 后才会唤醒nativeWake;
5)若 queue.next() != null ,queue.dispatchMessage() 根据 message.mTarget 找到对应的Handler;
6)在Handler.handleMessage()方法中处理message;
相关面试题
1.Handler内存泄露的原因?如何解决?
原因:内部类持有了外部类的引用;
new Handler(){//创建内部类
public void handleMessage(Message msg){
Activity.this.click(view);//引用外部类方法,根据JVM的可达性分析,Activity正在被handler引用,所以不被释放;
}
};
如上述事例:message持有handler,hanlder持有activity,message在没有被处理之前会一直占用内存空间;
解决方案:静态内部类+弱引用;案例如下:
private static class MineHandler extends Handler{
private WeakReference<Activity> refernce;
public MineHandler(Activity activity){
this.refernce = new WeakReference<>(activity);
}
}
而后在activity的onDestory()中MineHandler.removeCallbacksAndMessage()//清空管道和队列;
2.Message 和 obtainMessage 对象的创建方式、区别、如何维护消息池的?
创建方式及区别:
Message 是 new 出来的,频繁的 new 和 销毁 会造成内存抖动;
obtainMessage 是 享元模式 从MessagePool中取得;
如何维护:
static Message obtain(){
message m = sPool;//sPool为链表,将链表的第一个元素赋值给m;
sPool = m.next;//sPool链表指向下一个元素;
m.next = null;
m.flags = 0;
sPoolSize--;//sPoolSize最大容纳50个,obtain后,size--;
return m;
}
void recycleUnchecked(){
//Message在缓存进MessagePool前都会先置空,从而避免Message过大导致oom;
//判断当前消息池大小是否<50,是则将message插入到链表尾部,若>50直接丢弃;
synchronized(sPoolSync) {
if(sPoolSize < 50){
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
3.Handler的post和sendMessage的区别和应用场景?postDelayed后消息队列会有什么变化?
区别:
post更新UI操作可直接在runnable内重写;
post不需要外部创建消息对象,而是根据传入的runnable封装成消息对象;
应用场景:
sendMessage:当不需要实时显示的放到队列里运行时;
postDelayed:可用于定时器;
postDelayed后消息队列的变化:postDelayed后消息队列会进行重新排序;延迟时间会与消息队列中的message的执行时间进行比较,寻找位置插入消息;
4.Handler是怎么做到一个线程对应一个Looper的,如何保证只有1个MessageQueue?
Handler在构造时,首先得到线程中保存的Looper实例,再将Looper和MessageQueue关联;
Looper.prepare()在线程中只调用1次,MessageQueue也是在prepare()中与Looper关联的;
ThreadLocal 存储了当前线程中的 Looper 对象,因为ThreadLocal 只有一个setLooper()方法,所以保证了一个线程只有一个 Looper;
5.HandlerThread原理、好处、使用场景?
原理:HandlerThread继承Thread,在自己的线程中分发和处理消息;.start()启动,.quit()释放;
好处:
1)分担 MainLooper 工作量,降低主线程压力;
2)多线程,处理任务是串行执行的,按消息发送顺序处理;
3)拥有自己的消息队列,不会干扰或阻塞UI线程;
使用场景:子线程处理耗时、多任务操作、多网络请求、多文件I/O;
6.IdleHandler调用时机及其使用场景?
Looper.myQueue().addIdleHandler(new IdleHandler(){
public boolean queueIdle(){ //可以用来提升性能,在消息队列空闲时做事情;
return false; //返回true,重复使用,返回false,移除
}
});
调用时机:空闲时间;如果queue为空,最近待处理的message是延时消息,需滞后执行;
使用场景:
1)Activity启动优化:生命周期中耗时比较短,非必要代码放到idleHandler中,减少启动时间;
2)View绘制完成后,添加一个依赖于这个View的View,View.post()也可以实现;
7.消息屏障,同步屏障机制?
原理:
message的next()会判断 message.target == null ,若为空则循环找异步消息;
找到异步消息后,判断是否到处理时间,处理完成后从链表中移除;
移除屏障就是根据target和token找出屏障消息,从链表移除,并唤醒消息队列;
使用:
在Handler构造方法中传入async参数,设为true,此时handler添加的message都是异步的;
创建Message时,调用message.setAsynchronous(true);
移除屏障调用MessageQueue.removeSyncBarrier()
场景:
ViewRootImpl.scheduleTraversals()中使用了同步屏障,为了更快响应UI刷新速度;
8.Looper.quit() 和 quitSafely() 的区别?
quit():作用是把messageQueue中所有消息清空,无论是否是延迟消息;
quitSafely():只清空所有的延迟消息,并将非延迟消息派发给Handler处理;
两个方法调用后,都会调用nativeWake(),来唤醒block状态,之后才能释放线程;
9.主线程中的Looper.loop()为什么不会造成ANR?
造成ANR的原因:主线程在处理一个事件没有及时完成 或 被阻塞,导致当前事件无法执行;
结论:只要消息循环没有被阻塞,主线程就可以一直处理事件,不会ANR;
并且当消息队列读完,没有新消息进入的时候,主线程会进入睡眠状态,因此loop并不会对CPU有过多的性能消耗;