关于Handler的原理网上有很多,可以通过一些问题来进一步整理和理解

1.主线程+子线程的意义
一般来说,主线程是负责更新UI的,而子线程是做一些耗时的操作。
为什么子线程不能更新UI?
如果允许子线程去更新UI,由于多线程并发访问,可能会导致UI界面出现问题,比如一个线程关闭switch,另一个线程又开了switch。
多线程问题,一般都会通过加锁来解决,那如果是子线程+加锁机制呢?
a.UI访问和操作逻辑会更加复杂混乱
b.效率低下(可能会阻塞其他线程)
因此,切换到主线程来更新UI是一种简单且高效的选择。
为什么主线程不能做耗时操作?
答案就是,ANR!


    ANR会出现的原因如下:
  • 主线程对输入事件在5秒内没有处理完毕

  • 主线程在执行BroadcastReceiver的onReceive函数时10秒内没有执行完毕

  • 主线程在执行Service的各个生命周期函数时20秒内没有执行完毕

2.一个线程可以有几个Handler,几个Looper,几个MessageQueue对象
一个线程只能有一个Looper、一个MessageQueue,但可以有多个handler
简单的说,线程跟Looper和MessageQueue一一对应,Handler获取当前线程中的looper对象,looper用来循环获取存放在MessageQueue中的Message,再由Handler对Message进行分发和处理。
  MessageQueue(消息队列):用来存放通过Handler发送的消息,通常附属于某一个创建它的线程,在looper的构造函数中新建一个messageQueue的对象。可以通过Looper.myQueue()得到当前线程的消息队列。
  Handler:是Message的主要处理者,负责Message的发送,Message内容的执行处理。例如将消息发送到消息队列(sendMessage),更新UI组件(实现该类的handleMessage方法)
  Looper:是Handler和消息队列之间的通讯桥梁,程序组件首先通过Handler把消息传递给Looper,Looper把消息放到队列。Looper也把消息队列里的消息广播给所有的Looper。
  Message:消息的类型,理解为线程间交流的信息,处理数据后台线程需要更新UI,在Handler类中的handleMessage方法中得到单个的消息进行处理。

3.基本流程:
①Looper:包含一个MessageQueue和一个当前thread。
Looper.prepare(){
在当前线程绑定一个Looper;//记住只能有一个looper,如果已存在了looper则报错
}

Looper.loop(){
获取当前的looper;
获取与looper绑定的MessageQueue;
死循环{
不断获取Message;
分发msg.target.dispatchMessage(msg); // msg.target其实是handler
分发完msg.recycleUnchecked();
}

}

②handler
构造函数:获取当前的looper和MessageQueue
sendMessageAtTime():handler的各种sendMessage()最后追溯到这个方法,而这个方法则获取相关的MessageQueue来处理
enqueueMessage():获取上述的MessageQueue,并将之前loop中的msg.target赋值为当前的handler
dispatchMessage():调用了handlerMessage。

当handler.sendMessage的时候,msg就会进到当前线程绑定的MessageQueue中,然后msg.target就是当前的handler,与当前线程绑定的Looper就会不断地从MessageQueue中获取msg,然后调用handler的dispatchMessage来分发消息,也就调用了handler的hanlderMessage。

4.主线程中的Looper.loop()一直无限循环为什么不会造成ANR?


    造成ANR的原因一般有两种:
  • 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)

  • 当前的事件正在处理,但没有及时完成

ActivityThread.java 是主线程入口的类。ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。
Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。
再次强调,造成ANR是因为接收到的消息事件未被处理或处理

5.Handler内存泄露的原因?
①Handler导致内存泄漏一般发生在发送延迟消息的时候,当Activity关闭之后,延迟消息还没发出,那么主线程中的MessageQueue就会持有这个消息的引用,而这个消息是持有Handler的引用(msg.target=handler),而handler作为匿名内部类持有了Activity的引用,所以就有了以下的一条引用链。
主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity
其根本原因是主线程是不会被回收的,所以导致Activity无法被回收,出现内存泄漏,其中Handler只能算是导火索。下文会大概解释一下主线程ActivityThread。
②还有一个是ThreadLocal的内存泄露引起的
关于ThreadLocal简单的介绍一下:
ThreadLocal:不同线程获取同一个ThreadLocal对象时所得到的值均不一样,原因是threadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找对应的value值。也就是说,threadLocal所存储的值是跟线程一一对应的。
Thread里面有一个ThreadLocalMap来维护当前线程的值,而ThreadLocalMap里面有一个Entry[],这个Entry类是ThreadLocal的弱引用,并且有一个属性value,即Entry存了ThreadLocal的弱引用以及对应的value。

TheadLocalMap使用的是弱引用ThreadLocal作为key,value是一个强引用的object。当发生垃圾回收时,弱引用ThreadLocal key就会被回收,那么这时的map维护的就是(null,value)。此时假如线程一直存在,那么ThreadLocalMap也会一直存在,value强引用的object也一直存在。key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry[ ] -> Entry -> value
由于key为null,无法通过key找到对应的value,导致value无法被清空回收,就会一直存在。
所以除非线程结束,否则这条引用链会一直存在,永远无法回收,造成内存泄漏。

解决方式:ThreadLocal每次操作set、get、remove操作时,会相应调用 ThreadLocalMap 的三个方法,ThreadLocalMap的三个方法在每次被调用时 都会直接或间接调用一个 expungeStaleEntry() 方法,这个方法会将key为null的 Entry 删除,从而避免内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;

// 根据强引用的取消强引用关联规则,将value显式地设置成null,去除引用
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;

// 重新hash,并对table中key为null进行处理
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//对table中key为null进行处理,将value设置为null,清除value的引用
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}

当然也有一种可能就是,如果一个线程运行周期较长,而且将一个大对象放入LocalThreadMap后便不再调用set、get、remove方法的话,仍然有可能出现key的弱引用被回收后,引用没有被回收,此时仍然还是会导致内存泄漏。这样看起来问题确实无法解决,但这不应该由ThreadLocal去处理,而是需要开发者自行去调用remove,从而避免内存泄露。

最后补充HandlerThread:
HandlerThread是一个Thread中带有Looper,方便调用,防止在使用handler时忘记调用Looper.prepare()和Looper.loop()
handlerThread适用于子线程需要handler来通信的场景,在此场景下如果不用handlerThread,则需要自己新建一个线程,同时要调用Looper.prepare和Looper.loop
由于HanlderThread的run()方法是一个无限循环,因此当明确不需要再使用HandlerThread时,可以通过它的quit或者quitSafely方法来终止线程的执行。
使用:https://www.jianshu.com/p/9c10beaa1c95
解析:https://www.cnblogs.com/yongdaimi/p/11571166.html

关于handler和looper的源码,下文再继续,不然这一篇文章的内容就太多了。