1、一个线程有几个Handler?
一个线程有任意个Handler,可以new多个Handler,但最终同一线程多个Handler发的消息都在同一个Looper去处理。
2、一个线程有几个Looper?如何保证?
一个线程只有一个Looper。
首先看下Looper中有这么一个static final变量
//Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
会去new一个ThreadLocal对象,ThreadLocal是一个线程上下文变量,用于存储线程上下文。而在ThreadLocal中会有一个静态内部类,ThreadLocalMap。并且在Thread里面
//Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;
取到threadLocals是一个HashMap,HashMap是一个<key,value>键值对,key-value是一一对应的,所以在thread中只有一个threadLocals。
在Looper.prepare时,调用sThreadLocal.set(new Looper()),set里面是这样的
//ThreadLocal.java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
获取当前线程,并且取到当前线程的ThreadLocalMap,也就是刚才说的threadLocals。并set一个键值对<sThreadLocal,Looper>,并且是一一对应的。而且如果sThreadLocal对应有唯一值后,不能再被set替换,这就是为什么只能prepare一次。所以,一个sThreadLocal对应一个Looper。而sThreadLocal是static final变量,唯一的,所以一个线程中只有一个Looper。
3、Handler内存泄漏的原因?为什么其他的内部类没有说过这个问题?
首先Handler是匿名内部类,持有外部类的引用。再来看看MessageQueue中有大量的Message,而在发送Message时候把Handler与Message绑定。所以,Message中有持有Handler,Handler持有外部类的引用,因此Handler持有Activity的引用,Activity中有大量的内存。当Message是延迟消息时,Activity走了onDestroy(),导致Activity无法被释放回收。
解决问题是:在onDestroy里面清除MessageQueue中所有的Message;将handler声明为静态内部类,用软引用的或者弱引用持有Activity。
4、不断往MessageQueue里面放消息,为什么不会导致OOM?
首先,OOM的原因是找不到一个连续的想要大小的空间(有空间但是没有连续的想要大小的空间),而Message的空间是非常小的,只会导致手机内存空间不足,不会导致OOM。
5、为什么主线程可以new Handler?如果想要在子线程中new Handler要做哪些工作?
主线程在ActivityThread的main()中就做了new Handler的准备工作了;而在子线程new Handler时候,一般都会这么做:
class MyThread : Thread() {
private var mLooper:Looper? = null
override fun run() {
Looper.prepare()
mLooper = Looper.myLooper()
Looper.loop()
}
}
//
val myThread = MyThread()
myThread.start()
val handler = myThread.mLooper?.let { Handler(it) }
看上去没啥问题,但这样做有一个问题,因为Thread是异步线程,不能保证mLooper是取到值的。所以在子线程里面new Handler时候,要通过synchronized、wait和notifyAll来配合使用,这篇文章有讲到线程间通信。或者可以看看HandlerThread,就是这么实现的。
6、子线程中维护Looper,消息队列无消息的时候的处理方案是什么?有什么用?主线程呢?
假如Looper处理完后,没有退出,会导致子线程没有消息时候一直存在,导致内存泄漏。解决办法是调用MessageQueue中的quit
//MessageQueue.java
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
把mQuitting设为true,然后把消息队列的消息全部清空,再唤醒线程。
//MessageQueue.java
Message next() {
···
if (mQuitting) {
dispose();
return null;
}
···
}
返回null,Looper.loop()中取到null,退出死循环,loop循环结束,导致线程结束,线程结束内存释放。
主线程:主线程不能quit,主线程quit会报异常"Main thread not allowed to quit.",为什么?因为几乎从 App 启动,到 Activity 相关的生命周期,Services 相关的生命周期,到 App 退出等等,都是通过 Handler 来驱动的,如果把主线程的Looper退出死循环,那么后续将无法正常工作。
7、既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不用线程),那它内部是如何确保线程安全的?取消息呢?
在MessageQueue中,enqueueMessage是存消息的操作,其中有synchronized来保证每次只有一个Handler来操作enqueueMessage的过程:
//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
···
synchronized (this) {
···
}
···
}
而取消息呢,是通过next来取消息,同样里面有synchronized,但是取消息是只有在Looper.loop的时候去取,为什么要加synchronized?
//MessageQueue.java
Message next() {
···
synchronized (this) {
···
}
···
}
因为存消息的时候也用了synchronized,并且用同一个this作为监视器,所以操作的是同一个队列时,要保证存取操作不能同时发生。HashMap之所以线程不安全,是因为操作同一个链表时,存和取的操作同时发生。
8、使用Message时如何去创建?为什么?
Message中有一个消息池,使用完后的消息不会被GC回收,而是都会把消息内容都清除,并放进消息池中,可以被复用。这样做可以减少new Message的过程。每new Message,就会有一块内存分配给它;每分配一块内存,程序的可用内存也就少一块;当程序被占用的内存达到一定临界程度,GC 也就是垃圾回收器(Garbage Collector)就会出动,来释放掉一部分不再被使用的内存。被回收后,虽然内存空间变大了,但可能找不到一块连续容量大的内存,会出现OOM问题;而且,Android本身是基于Handler来处理消息的,假如每次都是new Message,在1S内会new上百个上千个Message,就会频繁的调用GC,导致内存抖动,而内存抖动会导致卡顿。
9、Handler的消息阻塞是怎么实现的?为什么主线程不会阻塞?
ANR产生的原因是Message没能在确定的时间内处理完成,而Looper是不管Message有没有处理完,只要MessageQueue里面有消息,那么就会出来去给Handler处理,当没有消息时,Looper会阻塞挂起。Looper的阻塞是因为没消息,所以ANR和Looper是死循环是两码事,没有关系。所以应用没有消息的时候,会处于睡眠状态,cpu会释放资源,而ANR是消息没及时得到处理。
最后,附上Handler源码解析。