Handler消息收发流程图
关键类介绍:
1、Handler
主要用于线程间通信,我们可以使用handler发送并处理消息,这些消息最后都会被封装成Message存放到MessageQueue中,然后通过Looper不断循环从MessageQueue中获取消息进行处理并分发给handler 主要方法:参考流程图
2、ThreadLocal
提供一个多线程环境下,每个线程可独立访问自己线程内部变量的机制;每个线程都有一个专属于自己的ThreadLocalMap
3、Looper
创建Looper时,默认创建一个ThreadLocal ActivityThread在main函数中,会调用Looper.prepareMainLooper,内部调用Looper.prepare给主线程创建一个独立的Looper,同时创建该Looper专属的MessageQueue(在构造函数中),同时把Looperset到当前线程的ThreadLocal内,并赋值给Looper中的sMainLooper; 所以每个线程都会有独立的Looper和MessageQueue 然后最后通过Looper.loop->Looper.loopOnce,通过取MessageQueue中的单链表Message进行消息循环,开启MessageQueue消息循环,取出消息时,通过msg.target获取当前msg的Handler,调用dispatchMessage进行消息回调
4、MessageQueue
MessageQueue 是消息队列的抽象类,用于存储消息和事件。每个线程都有一个对应的 MessageQueue 对象,用于存储该线程的消息和事件
(1) 入队
根据时间排序,当队列满时,阻塞,直到用户通过next取出消息。当next方法被调用,通知MessageQueue可以进行消息的入队
(2) 出队
由Looper.loop启动轮询器,对queue进行轮询。当消息达到执行时间就取出来。当MessageQueue为空时,队列阻塞,等消息队列调用enqueueMessage时,通知队列可以取出消息,停止阻塞
常见核心问题:
- 一个线程有几个Handler?
一个线程可以有多个Handler,因为Handler主要用于线程间进行通信,一个线程可以创建多个Handler来与其他线程进行通信。
- 一个线程有几个Looper?如何保证?
1个,Looper在prepare的时候,会通过当前线程的ThreadLocal进行存储,ThreadLocal针对每个线程都会有一个独立的ThreadlocalMap,里面以K-V形式存储Looper;如果属于第一次存储,会通过set方法,把当前Looper存储到Map集合;如果已经存储过了,那么会抛出异常,来保证每个Looper是唯一的
- Handler内存泄漏原因?为什么其他内部类没有这个问题?
Handler内部类会持有外部类的对象;其他内部类生命周期一致,Handler与Activity生命周期不一致,因为Handler内部消息实际是通过enqueueMessage存储信息,而Message的target会存储Handler; Message->Handler->Activity,根据JVM可达性算法,GC无法回收Activity,导致内存泄漏。
- 为何主线程可以new Handler?如果想要在子线程中new Handler,要做什么准备?
因为在ActivityThread中的main函数时,主线程已经通过Looper.prepareMainLooper创建了当前线程的Looper并且通过Looper.loop开启循环,所以在主线程可以直接new Handler;如果需要在子线程中new Handler,需要创建当前线程独有的Looper并且prepare,然后loop
- 子线程中维护的Looper,消息队列无消息时的处理方案是什么?有什么用?
next根据当前Message的when获取下一次取消息的时间间隔timeoutMilis,调用底层nativePollOnce进行阻塞等待,如果timeoutMilis为0,无需等待立刻返回;如果为-1,表示当前MessageQueue无消息,此处无限等待,直到有事件发生为止(比如enqueueMessage时,会调用nativeWake函数唤醒),如果为其他,则间隔timeoutMilis返回 底层:epoll机制 NDK层调用流程: (1) nativePollOnce(ptr,nextPollTimeoutMills)->底层Looper的pollOnce(timeMills)->pollInner(timeMills)->epoll_wait(mEpollFd,eventItems,EPOLL_MAX_EVENTS,timeoutMills)阻塞等待,此处只是作为一个最大等待时间,所以过程有其他Message进行enqueue操作时,会提前停止阻塞 (2) nativeWake(ptr)->nativeMessageQueue.wake()->nativeLooper.wake()->往mWakeEventFd中write“1”,唤醒looper 所以子线程无消息时如果不处理,会一直等待,此时需要调用quit,唤醒线程同时退出Looper
- 既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程),那它内部是如何确保线程安全的?
锁;enqueueMessage->synchronized(this):内置同步锁(锁与解锁由JVM完成),控制不同线程访问当前MessageQueue对象时,存在synchronized(this)的方法(如enqueueMessage->next取数据时),都必须等待当前方法执行完成才能继续执行;同时因为ThreadLocal保证了一个线程只有一个MessageQueue,所以保证了一个线程只有一个可以操作当前MessageQueue的地方
- 为什么主线程是个死循环而不会退出?
Android程序在ActivityThread的main函数中进行启动,通过死循环不断从MessageQueue中取消息进行界面刷新,正是因为有这个死循环,才能保证程序运行,如果跳出循环了,程序也就退出了。
- MessageQueue的数据结构是什么?Message呢?为什么要采取这样的数据结构
MessageQueue是个优先级队列,通过时间顺序控制Message的进出,Message是个单链表,因为Message要频繁插入和移除,使用链表效率更高,使用数组的话,需要考虑扩容问题,且频繁插入移除不适合使用数组
- ThreadLocal是什么?ThreadLocalMap内部为什么要采用弱引用的方式?
ThreadLocal主要为了提供多线程一个可以访问线程内部变量而产生的一个类,而ThreadLocal内部实际时通过ThreadLocalMap存储线程本地变量,而Entry对象中的key实际是一个ThreadLocal对象的弱引用,弱引用实际是为了避免内存泄漏的问题,如果在ThreadLocalMap中使用强引用来引用ThreadLocal对象,而该ThreadLocal对象又在被其他对象强引用,那么会导致ThreadLocal对象无法被GC回收,出现内存泄漏