Android进阶 - Handler底层源码分析二
- 一个线程有几个 Handler?
- 一个线程有几个 Looper?如何保证?
- Handler内存泄漏原因? 为什么其他的内部类没有说过有这个问题?
- 为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?
- 子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
- 既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?取消息呢?
- 我们使用 Message 时应该如何创建它?
- Message 的数据结构是什么样子?
- Looper死循环为什么不会导致应用卡死
- HandlerThread是什么?
接 上一篇本篇带领大家分析handler常见面试题.
一个线程有几个 Handler?
一个线程中可以有多个Handler,new几个就有一个
一个线程有几个 Looper?如何保证?
一个线程中只能有一个Looper,
在调用Looper.prepare()的时候,会通过ThreadLocal来初始化Looper,
Handler.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
通过sThreadLocal.get()方法来判断当前TheradLocal是否有值,如果有值则提示Only one Looper may be created per thread异常,如果没有Looper通过set()方法设置Looper,所以一个线程只能存在一个Looper
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在Looper初始化的时候会初始化MessageQueue(),所以一个线程中耶只有一个MessageQueue()
Handler内存泄漏原因? 为什么其他的内部类没有说过有这个问题?
Handler内存泄漏是因为内部类持有外部类的引用
在这里Handler持有Activity的引用,因为Handler可以调用到Activity的方法
在调用Handler.sendXXX/postXX发送消息的时候,会调用到enqueueMessage()方法,msg.target会把Handler对象保存下来,所以Message持有Handler对象
class Message{
Handler target;
}
class Handler{
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
}
如果现在有一个消息需要等待5分钟来执行,但此时页面已经关闭了,消息还在队列中等待执行,那么Message持有Handler对象,Handler持有Activity对象,所以Activity不会被GC回收导致内存泄漏.
其他的内部类为什么不会内存泄漏,例如RecyclerView中的ViewHolder?
因为生命周期不同,ViewHolder在RecyclerView消失的时候也会消失.
为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?
因为在启动的时候,ActivityThread中系统帮我们调用了Looper.prepare() 和Looper.loop()方法
方式一:
new Thread(() -> {
Looper.prepare();
TextView tv = findViewById(R.id.tv);
tv.setText("青岛啤酒111");
Looper.loop();
}).start();
效果图(1.1)
方式二:
//传入参数 = 线程名字
HandlerThread handlerThread = new HandlerThread("TextHandler");
//启动线程
handlerThread.start();
//消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
Handler handler1 = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
TextView tv2 = findViewById(R.id.tv2);
tv2.setText("当前线程名为:"+Thread.currentThread().getName());
}
};
// 使用线程Handler向工作线程的消息队列发送消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
// 通过Handler发送消息到其绑定的消息队列
handler1.sendMessage(msg);
效果图(1.2)
参考自:www.jianshu.com/p/9c10beaa1…
子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
调用Looper.quit()方法
-
Looper.quit():
- 调用后直接终止Looper,不在处理任何Message,所有尝试把Message放进消息队列的操作都会失败,比如Handler.sendMessage()会返回 false,但是存在不安全性,因为有可能有Message还在消息队列中没来的及处理就终止Looper了。
-
Looper.quitsafely()
- 调用后会在所有消息都处理后再终止Looper,所有尝试把Message放进消息队列的操作也都会失败。
参考自:www.jianshu.com/p/2c7c7863d…
既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?取消息呢?
class MessageQueue{
//往队列中添加消息
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
if (needWake) {
//native底层 唤醒
nativeWake(mPtr);
}
}
return true;
}
//取出队列中的消息
Message next() {
...
for (;;) {
//native底层实现堵塞,堵塞状态可被新消息唤醒,头一次进来不会延迟
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
}
}
在enqueueMessage()存消息和next()取消息的时候,都用了synchronized()同步锁,将代码都同步了
消息的读取也是同理,也会拿当前的 MessageQueue 对象作为锁对象,来保证多线程读写的一个安全性。
我们使用 Message 时应该如何创建它?
- 一种是直接 new 一个 Message 对象,
- 另一种是通过调用 Message.obtain() 的方式去复用一个已经被回收的 Message,
当然日常使用者是推荐使用后者来拿到一个 Message,因为不断的去创建新对象的话,可能会导致垃圾回收区域中新生代被占满,从而触发 GC。
Message 中的 sPool 就是用来存放被回收的 Message,当我们调用 obtain 后,会先查看是否有可复用的对象,如果真的没有才会去创建一个新的 Message 对象。
补充:主要的 Message 回收时机是:
- 在 MessageQuese 中 remove Message 后;
- 我们主动调用 Message 的 recycle 方法后;
Message 的数据结构是什么样子?
单链表,Message 中会通过 next 来持有下一个 Message 对象的引用
class Message{
Message next;
}
Looper死循环为什么不会导致应用卡死
class Looper{
public static void loop() {
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
}
}
}
来看看MessageQueue的中的next代码:
class MessageQueue{
//往队列中添加消息
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
if (needWake) {
//native底层 唤醒
nativeWake(mPtr);
}
}
return true;
}
//取出队列中的消息
Message next() {
...
for (;;) {
//native底层实现堵塞,堵塞状态可被新消息唤醒,头一次进来不会延迟
nativePollOnce(ptr, nextPollTimeoutMillis);
}
}
在MessageQueue.next()方法,获取消息的时候会通过nativePollOnce()实现堵塞,堵塞状态可被新消息唤醒,
在MessageQueue.enqueueMessage()往队列中添加消息的时候,会通过nativeWake()唤醒
简单理解:
一般情况下,Looper.loop()方法在阻塞状态,如果现在有消息会执行到MessageQueue.enqueueMessage()会把loop()唤醒,然后调用MessageQueue.next()取消息,之后又会把loop()阻塞
HandlerThread是什么?
HandlerThread是Thread的子类,严格意义上来说就是一个线程,只是它在自己的线程里面帮我们创建了Looper
如有不足,欢迎评论补充~
原创不易,您的点赞就是对我最大的支持~