目标:
1.Handler和Looper什么关系?
一个Looper对应一个MessageQueue,可以多个handler往MessageQueue发送消息。
2.一个线程有几个Handler?
3.Handler内存泄漏的原因?
4.使用Message时如何创建它?
5.子线程维护的Looper, 消息队列无消息时的处理方案是怎么样的?有什么用?
6.为什么主线程可以new Handler?如果想要在子线程new Handler,应该怎么办?
7.线程间通信原理是怎么样的?
8.Looper死循环为什么不会导致应用卡死?
9.既然可以存在多个Handler往MessageQueue中添加数据(发消息的各个Handler可能位于不用的线程),那么它内部是如何保证线程安全的?
一、Handler机制
1.1 Handler是什么?
Handler机制是Android提供的消息通信机制。
它涉及多个关键组件,包括Handler、Looper、MessageQueue和Message,共同协作以实现消息的发送、接收和处理。
Handler:负责发送消息和处理消息。当发出一个消息后,首先进入一个消息队列,发送消息的函数即刻返回,而另一个部分在消息队列中逐一将消息取出,然后对消息进行处理。这种机制通常用来处理相对耗时比较长的操作。
Looper:负责循环读取MessageQueue中的消息,读到消息之后就把消息交给Handler去处理。
MessageQueue:存储消息对象的队列。
Message:消息对象,是Handler机制中传递的基本单位。
1.2 Handler工作原理
Handler机制的工作流程大致如下:
1) 创建Handler:首先需要创建一个Handler对象,其构造函数中的参数如async和callback用于确定消息的处理方式。async参数确定用Handler发送的消息是否要设置成异步消息,而callback参数则允许在dispatchMessage回调时优先调用callback中的代码。
2)发送消息:通过调用Handler的sendMessage或post等方法发送消息到MessageQueue中。
3)处理消息:Looper循环读取MessageQueue中的消息,并将它们分发给相应的Handler进行处理。处理过程包括调用Handler的handleMessage方法(如果存在回调,则优先执行回调)。
Handler机制在Android开发中特别重要,因为它允许在主线程中更新UI,同时避免阻塞主线程。通过在子线程中执行耗时操作,并通过Handler将结果发送回主线程进行UI更新,可以实现线程间的有效通信和UI的平滑更新。
二、线程间内存共享
MessageQueue是线程间共享的消息容器,用于管理消息。
完成线程间通信的原理:内存共享,通过共享MessageQueue消息队列,实现主线程和子线程通信。
三、消息Message
3.1 消息排序
按照msg.updateTime进行排序,相对于系统开机时间的时间戳。
3.2 消息创建
消息创建采用享元模式,增加消息对象复用。
1)消息存在一个缓冲池sPool(大小为50个),缓存使用完的消息对象;
2)创建消息的时候,优先从sPool中拿取一个消息对象;
3)消息使用完成后,将消息字段重置,然后添加到sPool,用于复用。
四、Handler内存泄漏和解决方案
4.1 Handler内存泄漏的原因
内存泄漏原因:JVM回收的时候问题,根可达算法,被JVM GC Roots直接或者间接引用的对象不能够被回收。
Activity中,
Handler handler = new Handler();
handler是一个匿名内部类,持有Activity的this对象;
ActivityThread的
static sMainLooper: 是一个静态变量,作为GC Roots
static sThreadLocal->Looper-->MessageQueue-->Message-->Handler-->Activity
msg.target = this;(Handler)
采用delay发送的消息,引用链仍然存在。
4.2 解决内存泄漏的方法
打断引用链。
1.Handler声明为静态内部类对象,不持有Activity引用
2.Handler采用弱引用Activity对象
Looper执行的动力:线程提供的。
EventBus、Retrofit与主线程通信,都需要借助Handler
五、Handler如何正确创建
5.1 主线程
主线程为什么创建Handler不需要指定Looper?
Handler Handler = new Handler();
主线程main运行的时候,从主线程Looper.mainLooper()
5.2 子线程撞见
1)先创建Looper
创建以后,调用Looper.prepare()
Looper.prepare
2) 初始化Handler
3)Looper.loop()
5.3 子线程Handler HandlerThread
封装了Handler
HandlerThread获取Looper
public Looper getLooper() {
if (!isAlive()) {
return null;
}
boolean wasInterrupted = false;
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
wasInterrupted = true;
}
}
}
/*
* We may need to restore the thread's interrupted flag, because it may
* have been cleared above since we eat InterruptedExceptions
*/
if (wasInterrupted) {
Thread.currentThread().interrupt();
}
return mLooper;
}
为什么需要采用while
因为如果是别的线程唤醒,如果Looper还没有初始化,需要继续等待Looper初始化。因此需要采用while。直到Looper有值。
wait挂起:挂起线程,并且释放锁
sleep:
六、Handler多线程安全
消息入列
消息获取:MessageQueue.next() 拿取一个消息
多个Handler往MessageQueue发送消息,如何保证线程安全性。
6.1 存消息的时候
boolean MessageQueue.enqueueMessage(Message msg, long when)
采用加锁的方式
synchronized (this) {
6.2 取消息的时候
Message MessageQueue.next()
synchronized (this) {
存消息和取消息的时候,同步进行。
6.3 Loop与ThreadLocal
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Looper通过ThreadLocal进行缓存。
每个线程对应于自己的Looper。
七、同步消息和异步消息
消息屏障:target==null的消息。保证异步消息优先执行(UI刷新优先)
异步消息:isAync=true是异步消息,用于UI刷新。view.scheduleTraversals
同步消息:
添加消息屏障:
postSyncBarrier: 插入一个消息屏障
removeSyncBarrier: 移除消息屏障
丢帧;
skip 30 frames!
八、Looper.loop死循环为什么不会发生ANR问题
等待和休眠
1)没有消息的时候,阻塞等待消息到来;
2)等待消息执行时间:没有到消息可执行时间,epoll机制进行超时等待。
8.1 epoll机制
涉及到I/O
select: 非租塞忙轮询方式。没有数据的会出现CPU空转,浪费资源。直到一系列I/O事件存在,但不知道是哪几个流存在事件。
epoll_wait: 有N个I/O事件,一个线程处理多个I/O事件。没有I/O事件的时候进行阻塞,