目录
1.请大致讲下handler的工作原理
2.handler.postDelay原理
3.一个线程有几个Looper?几个Handler?
4. Handler内存泄漏原因?以及解决方案
5.为何主线程可以new Handler如果想要在子线程中new Handler要做些什么准备?
6.消息退出是调用什么
7.loope死循环为什么不会造成应用卡顿
8.post跟send的区别
1.请大致讲下handler的工作原理
handler的工作流程基本包括handler、message、message queue、looper这4个部分,
其中handler是消息的处理者,具备发生、处理、获取等功能,
message消息实体,可以带数据obj,行为标志符what和一串动作Runable,其中Runable转为了message的callback变量。
message queue消息队列,以队列的形式对外提供插入和删除操作,内部实际是一个单链表的数据结构,其中的消息的插入是根据传入的when进行插入的,when最短的插入到头部,时间最长的插入到尾部,其余消息根据when的大小对消息进行排序插入,遵循先进先出的原则。
looper,循环,它负责从消息队列中取出消息,然后把消息交给handler来处理
处理流程就是handler通过调用sendMesssage,实际是调用sendMessageAtTime发送when为0+当前手机启动到现在的时间为0到消息队列中的enqueue方法进行处理,消息队列检测到有消息后,判断当前when是否为0,或者when是否要小于于链表头个消息的when,或者当前没消息,这几种情况下就把message插入到链表头,然后唤醒looper,调用next方法获取链表头消息,分发个msg.targer进行dispatchMessage处理,然后判断msg中是否有runable,如果没有的话,就执行handlerMessage了
2.handler.postDelay原理
这块在上篇文章handler解析(2) -Handler源码解析_沙滩捡贝壳的小孩的博客-CSDN博客已经了解过了handler.postDelay本质上都是调用handler.sendsendMessageDelayed,然后调用sendMessageAtTime,最后调用enqueueMessage,然后我们知道MessageQueue会根据post delay的时间排序放入到链表中,链表头的时间小,尾部时间最大。在next取消息的时候,拿当前时间跟链表头的时间做对比,如果头部msg是有延迟的,而且延迟时间还没到的,则进行阻塞,然后等于是开了个定时器,等到时间到了的话,则进行唤醒操作,然后进行next操作取出消息,进行分发
举例:
postDelay()
一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()
阻塞,Looper阻塞;- 紧接着
post()
一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()
方法唤醒线程; MessageQueue.next()
方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;- Looper处理完这个消息再次调用
next()
方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()
阻塞; - 直到阻塞时间到或者下一次有Message进队;
3.一个线程有几个Looper
?几个Handler
?
这块在上篇文章handler解析(2) -Handler源码解析_沙滩捡贝壳的小孩的博客-CSDN博客也说过了,一个线程只能有一个looper,这块是由ThreadLocal中ThreadLocalMap控制的,在loop.prepare中ThreadLocal进行set一个looper对象操作,而且ThreadLocal是static final类型的,但是handler就可以有无数个了,因为只要它关联到当前线程的looper就行了,这块在handler的构造方法中进行了声明
4. Handler
内存泄漏原因?以及解决方案
泄漏原因:
Handler允许我们发送延迟消息,如果在延迟期间用户关闭了Activity,那么改Activity会泄漏。这个泄漏是因为Message会持有Handler,而又因为Java的特性,内部类会默认持有外部类的引用,使得Activity会被Handler持有,这样最终就会导致Activity泄漏。
解决方法:
静态内部类 + 弱引用
private static class MyHandler extends Handler {
//弱引用,在垃圾回收时,activity可被回收
private WeakReference<MainActivity> mWeakReference;
public MyHandler(MainActivity activity) {
this.mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
}
5.为何主线程可以new Handler
如果想要在子线程中new Handler
要做些什么准备?
每一个handler都要对应一个looper,主线程因为在AcitivityThread中的Looper.prepareMainLooper中已经创建好looper了,所以在new Handler中会直接通过ThreadLocal.get方法获取到looper。但是子线程就没有创建好looper了,所以在new handler的时候会因为ThreadLocal.get获取不到looper报错,所以需要在new Handler之前调用Looper.prepare();在之后调用Looper.loop(); Looper.myLooper().quitSafely();
6.消息退出是调用什么
消息退出是调用Looper的quit或者quitSafely,其中quitSafely()与quit()方法的区别是,quit()会直接回收消息队列中的消息,而quitSafely()会根据当前的时间进行判断,如果消息的meesage.when比当前时间大,那么就会被回收,反之仍然被取出执行。然后这个时候在发送消息时,消息是会被回收的
7.loope死循环为什么不会造成应用卡顿
线程对于java代码来说就是一段可执行代码,当可执行代码执行完了之后,程序就终止了,所以对于主线程来说,肯定不想就这么终止,所以死循环是必要的,可以保证一直执行下去,其次应用卡顿是在生命周期中执行耗时操作导致掉帧,甚至发生ANR,looper.loop本身是不会导致应用卡死的
再者说了,主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
8.post跟send的区别
post之前文章也分析过了,本质上还是调用sendMessageAtTime发送一条消息来着,只是说这条消息的赋值对象是msg.callBack,在next方法中优先执行.callBack中的方法,所以handler.post也可以用来直接在子线程中更新UI
send跟post本质上没有区别,post是send使用的简化版