1、Handler 实现机制
Handler 机制有几个核心类:Handler、Looper、Message、MessageQueue。
Handler 机制是一个典型的生产者消费者模式——多个生产者,一个消费者,该模式是处理线程安全的一个经典模式
Message
Message 是 Handler 接收和处理的消息对象,内部使用链表实现一个消息池,用于重复利用,
避免大量创建消息对象,造成内存浪费。用于在不同线程之间交换数据。
Handler
主要用来发送和处理消息。发送消息一般是使用 Handler 的 sendMessage()方法,消息经过处理后,最终传递到 Handler 的 handlerMessage()方法中。
MessageQueue
消息队列,它主要用来存放所有通过 Handler 发送的消息,这部分消息会一直存在于消息队列中,等待被处理。注意:每个线程中只会有一个 MessageQueue 对象。
Looper
每个线程中 MessageQueue 的管理者,调用 Looper 的 loop()方法后,就会进入到一个无限循环当中,每当发现 MessageQueue 中存在一条消息,就会将其取出传递到 Handler 的 handleMessage()方法当中。
2、Handler 创建流程
Looper.prepare
public static void prepare() { prepare(true); } 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)); }
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
创建了 Looper 对象,在构造函数中创建了 MessageQueue
然后将 Looper 对象通过 ThreadLocal 跟当前的主线程绑定
new Handler()
通过 Looper 类中的 ThreadLocal 从主线程中获取到 Looper 对象
然后通过 Looper 对象获取到 MessageQueue 的应用
handler.sendMsg()
找到 MessageQueue 对象
然后 msg.target=this,将 handler 对象成为 msg 的一个成员变量
最后把 msg 添加到 MessageQueue 中
Looper.loop()
找到 MessageQueue
然后开启死循环遍历 MessageQueue 消息池
当获取到 msg 的时候,通过 msg.target 找到发送这个 msg 的 handler
然后调用 handler 的 dispatchMessage()方法
3、一个线程有几个 Handler?
一个线程可以用有多 Handler,因为 Handler 最终是被 Message 持用的(post 里面的 Runnable 最终也会被包装成一个 Message),以便 Looper 在拿到 Message 后调用 Handler 的 dispatchMessage 完成回调,而且项目中仔细去看也确实如此,我们可以每个 Activity 中都创建一个 Handler 来处理回调到主线程的任务。
4、一个线程有几个 Looper?
一个线程只能拥有一个 Looper,这里从源码中就可以看到,sThreadLocal.set 只调用了一次,如果再次调用 prepare 会判断 sThreadLocal.get 是否为空,如果不为空就直接抛出异常了,也就是同一线程多次调用 prepare 方法会直接崩溃,这里也是避免了程序去修改某个线程已经设置好的 Looper 值。
5、为何主线程可以使用 Handler?如果想要在子线程中使用 Handler 机制要做些什么准备?
Handler 的构造中(无论调用哪个最终都会走到这里),是需要判断当前线程是否存在 Looper 的,如果不存在会直接抛出异常,主线程之所以可以使用 Handler 是因为系统帮在 ActivityThread 中已经帮我们创建了 Looper 并且已经让它运行了起来。
如果我们现在子线程中使用 Handler 的话,,其实就是两步,在子线程中调用 Looper.prepare() 和 Looper.loop() 即可,prepare 帮我们在对应线程创建 Looper,loop 让刚刚创建好的 Looper 运行起来。
6、我们使用 Message 时应该如何创建它?
创建的它的方式有两种,一种是直接 new 一个 Message 对象,另一种是通过调用 Message.obtain() 的方式去复用一个已经被回收的 Message,当然日常使用者是推荐使用后者来拿到一个 Message,因为不断的去创建新对象的话,可能会导致垃圾回收区域中新生代被占满,从而触发 GC。
通过 Message 的静态方法 Message.obtain();
通过 Handler 的公有方法 handler.obtainMessage()。
7、可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?
存储消息
在往消息队列里面存储消息时,会拿当前的 MessageQueue 对象作为锁对象,这样通过加锁就可以确保操作的原子性和可见性了。
读取消息
读取消息也是同理,也会拿当前的 MessageQueue 对象作为锁对象,来保证多线程读写的一个安全性。
8、handler postDelay 这个延迟是怎么实现的?
handler.postDelay并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以MessageQueue的时间顺序排列和唤醒的方式结合实现的。
Handler.postDelayed()精确延迟指定时间的原理_沙漠一只雕得儿得儿的博客-CSDN博客
9、looper 的无限循环为什么不会ANR?
安卓是由事件驱动的,Looper.loop不断的接收处理事件,每一个点击触摸或者Activity每一个生命周期都是在Looper.loop的控制之下的,looper.loop一旦结束,应用程序的生命周期也就结束了。
思考一下发送ANR情况:事件没有得到处理;事件正在处理,但是没有及时完成。 对事件进行处理的就是looper,所以只能说事件的处理如果阻塞会导致ANR,looper的无限循环不会导致ANR。
而且主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
10、Handler 引起的内存泄露原因以及最佳解决方案?
原因:Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就 导致 Activity 泄露。
解决:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并在 Acitivity 的 onDestroy()中调用 handler.removeCallbacksAndMessages(null)及时移除所有消息。