最近花了几天研究了一下Android的Handler
机制。也阅读了网上很多的资料,于是在此写篇文章整理一下,想形成自己的体系。有一些别人总结的很好的干货我这边就不再总结了(但是会给出推荐链接),我只是把自己认为特别重要的写在这里。
一.Handler
机制是干啥的
我认为可以从两方面来理解
1 从侠义上来说,handler
(Android消息机制的主要成员)是用来解决子线程无法访问更改UI的问题的
2 从广义上来说,所有的代码都是在handler
基础上运行的,handler
是Android的app运行的整个框架。Android系统框架内,Activity
生命周期的通知等功能也是通过Handler消息机制来实现的
1好理解,因为这是handler
与我们距离最近的使用。但是为什么我认为有2这么一点呢?下面我来解释下:
关于 2 的解释
我们知道,安卓应用程序作为一个控制类程序,跟Java程序类似,都是有一个入口的,而这个入口就是ActivityThread
,ActiviyThread
也有一个main
方法,这个main
方法其实是安卓应用程序真正的入口。所以
①我们进入ActivityThread
的main
方法看一下
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
省略无关代码,我们不难发现,在main
函数这里进行了两个关于Looper
的至关重要的操作,第一个是Looper.prepareMainLooper();
②我们追踪进入
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
发现这里是对sMainLooper
进行赋值,就是给主线程的looper
赋值(looper
是handler
机制里面一个非常重要的角色)也就是创建了主线程的Looper
③然后Looper.loop()
,我们追踪进入loop
方法
public static void loop() {
...
for (;;) {
...
}
}
发现在loop
方法里面,除了开头几行的一些赋值等等,后面就会进入一个死循环,我们整个app的启动,可以说都是在这个死循环里面进行的。可以这么理解,就是可以把我们的app比喻成人,这个死循环比喻成自然环境。只有自然环境能够一直运行下去,正常运转,那么人才能有生活的物资,才不至于灭亡。所以app就依赖这个死循环才能在你不退出的情况下一直运行。所以我说2 不过分吧?
二.Handler
机制的几个角色及其相互关系
上面我们解决了两个问题,其一是Handler
机制是啥(主线程中更新UI),其二是Handler
机制的重要性(Android的framework
层的极端重要的部分)。那么下面就来详细地介绍下Handler
机制涉及的几个角色Handler,Looper,MessageQueue,Message
及其相互关系
1.基本介绍
-
Handler
: 发送消息和处理消息 -
Looper
: 从MessageQueue中获取Message
,并将其交给Handler
的dispatchMessage(Message)
方法 -
MessageQueue
: 消息队列,存放Handler
发送过来的消息 -
Message
: 消息的载体
2.他们之间的关系
他们之间的关系,我从两个方面来分析
①其一,从基本功能的角度
首先,看这么一张图
(1)MessageQueue
就像履带。它上面装载着一个个Message
(2)Thread
就像传送带的动力,对应到程序就是我们通信都是基于线程而来的。
(3)传送带的滚动需要一个开关给电机通电,那么就相当于我们的Looper
的loop
函数,而这个loop
里面的for
循环就会带着传送带不断的滚动,去轮询messageQueue
上面装载的每一个Message
(4)Message
就是 我们的货物了。
②其二,从设计思想的角度
其实构成了线程模型中的经典问题 生产者-消费者模型。
生产者-消费者模型:生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加数据,消费者从存储空间中取走数据。
好处:能够保证数据生产消费的顺序(通过MessageQueue
,先进先出) 不管是生产者(子线程)还是消费者(主线程)都只依赖缓冲区(handler
),生产者消费者之间不会相互持有,使他们之间没有任何耦合
③再总结下,就是下面这张图
三.关于Handler
机制的一些必要知识点
1.Handler
有哪些发送消息的方法
sendMessage(Message msg)
sendMessageDelayed(Message msg, long uptimeMillis)
post(Runnable r)
postDelayed(Runnable r, long uptimeMillis)
sendMessageAtTime(Message msg,long when)
//下面这些不怎么重要
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long uptimeMillis)
sendEmptyMessageAtTime(int what, long when)
2.从Handler
发送消息到消息入队,其内部是如何实现的?
①首先在子线程调用sendMessage
方法发送消息
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
②追踪sendMessageDelayed
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
③发现调用了sendMessageAtTime
,继续追踪
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
④发现调用了Handler
的enqueueMessage
,继续追踪
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
//将当前的Handler赋值给Message的target属性
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
⑤发现最终调用了MessageQueue
的enqueueMessage
方法,将消息入队
2.MessageQueue
的必要知识
(1)基本介绍
MessageQueue
维护了一个优先级队列(以时间为顺序进行排列),而且是以链表的形式实现的。其他线程若想发送消息到此线程,则就要把消息发送到此线程的MessageQueue
。
(2)排序依据when的介绍
MessageQueue
不仅满足先进先出,而且还有顺序。其顺序就是根据每一个消息的when
属性来排序的。还记得一开始列出来的提交信息的那些方法嘛。不管用哪个方法去提交消息,最终一般都会走到sendMessageDelayed
方法,而这个方法就可以设置when
,我们进去看一下
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
可以看到这个when
值是 SystemClock.uptimeMillis()
与 delayMillis
之和。代表从系统启动开始再加上你给它设置的延迟时间数。很好理解的。when
就是用于表示 Message
期望被分发的时间,就是啥时候执行。
(3)在哪根据when来排序的
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.when = when;
...
if (p == null || when == 0 || when < p.when) {
...
} else {
...
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
//***************************
//***************************
//看这里看这里
//***************************
//***************************
break;
}
if (needWake && p.isAsynchronous()) {
...
}
}
...
}
...
}
...
}
我们把相对无关的代码都省去了。在代码标注的位置,我们可以看到这个for
死循环跳出的条件有两个。其一是一直遍历走到MessageQueue
的队尾,才退出循环,其二是发现有符合要求的地方可以插入message
了。这个符合要求是啥意思。比如待插入的message
的when
是2,而MessageQueue
队列中的message
分别是1,3,4,5.
那么待插入的message
就会插入到when
为1
和3
的message
之间,新队列的when
就变成了1,2,3,4,5
。很好理解吧。
特殊情况,如果
when
相同,则按代码执行时间先后顺序插入。
(4)MessageQueue
核心算法enqueueMessage
的完整分析
上面我们简简单单地分析了下enqueueMessage
,下面我们就较为完整地分析下这个算法
boolean enqueueMessage(Message msg, long when) {
// Hanlder为空则抛异常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//当前消息如果已经已经被执行则抛异常
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// 对MessageQueue进行加锁
synchronized (this) {
// 判断目标thread是否已经死亡
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
// 标记Message正在被执行,以及需要被执行的时间
msg.markInUse();
msg.when = when;
// p是MessageQueue的链表头
Message p = mMessages;
// 判断是否需要唤醒MessageQueue
boolean needWake;
// 如果有新的队头,同时MessageQueue处于阻塞状态则需要唤醒队列
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//...
// 根据时间找到插入的位置
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
//...
}
msg.next = p;
prev.next = msg;
}
// 如果需要则唤醒队列
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
需要注意的是:当新插入的Message
在链表头时,如果messageQueue
是空的或者正在等待下个延迟消息,换句话说就是处于阻塞状态的时候,则需要唤醒MessageQueue
3.Looper
的必要知识
(1)简单介绍
Looper
是个什么角色呢?从一个简单的例子来说吧,我们在子线程创建Handler
实例的时候,必须要先创建一个Lopper
,并开启循环读取消息。在主线程中我们ActivityThread
已经帮我们创建了(在本文最开始的地方就有介绍),所以我们不需要再次创建。但是在子线程中新建Handler
的时候就需要创建咯!比如这样
public static class MyThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(@NonNull Message msg) {
//处理接收的消息
}
};
Looper.loop();
}
}
一个线程对应一个Looper
,但是可以有多个Handler
。Looper
是消息轮询的基础。比如本文最开始的传送带图片,没有Looper
那么整个Handler
机制就无法运转,而且Looper
还不能多,否则传送带就会崩掉,违背单线操作的概念,造成不合适的并发操作。
- 值得注意的是,我们看下
Looper
的构造方法,发现我们在创建Looper
的时候,也会捎带着创建MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
(2)Looper
的工作介绍(消息是怎么被读取的)
刚刚说了用Loop
来轮询消息,那么具体是怎么个询法呢?没错,就是Looper
的loop
方法。我们要想搞懂Looper
的消息轮询机制,就要从loop
方法开始。(本文开始也有一丢丢对loop
方法的分析,不过这里才是更全面的)
public static void loop() {
...
for (;;) {
Message msg = queue.next(); // might block
...
}
}
我们可以看到,在开启死循环后,获取每一个消息的方式是调用queue
的next
方法,而且值得注意的是,在源码的注释中给了两个单词:might block
,即有可能是阻塞的。好,下面追踪queue
的next
方法看看
Message next() {
// Return here if the message loop has already quit and been disposed.
// 源码中的注释表示:如果looper已经退出了,这里就返回null
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//...
// 定义阻塞时间赋值为0
int nextPollTimeoutMillis = 0;
//死循环
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 阻塞对应时间 这个方法最终会调用到linux的epoll机制
nativePollOnce(ptr, nextPollTimeoutMillis);
// 对MessageQueue进行加锁,保证线程安全
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//...
if (msg != null) {
if (now < msg.when) {
// 将要获取的下一个消息(也就是队头)还没开始,计算等待时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获得消息且现在要执行,标记MessageQueue为非阻塞
mBlocked = false;
// 链表操作
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 没有消息,进入阻塞状态
nextPollTimeoutMillis = -1;
}
//退出
if (mQuitting) {
dispose();
return null;
}
}
}
总结下:先看下Looper
是否已经退出(怎么退出后面讲),然后进入死循环,首先判断是否要进行阻塞(判断是依据是上一次循环得到的某个值:nextPollTimeoutMillis
),阻塞最终会调用到linux
的epoll
(pipe/epoll
)机制,阻塞结束后会进入锁中,得到队首的Message
对象,然后进入if
判断,如果有消息,即把队头消息取出,然后进行判断,决定是当下立即返回还是delay
一下再返回。如果没有消息,则无限期进入阻塞状态,直到有消息进入。
-
nextPollTimeoutMillis
表示阻塞的时间,其中,-1
表示无限时间,直到有消息传入为止,0
表示不阻塞。
(3)Looper
的退出
刚刚说了Looper
的工作原理,那么工作完了怎么退出呢?Looper
提供了quit
和quitSafely
两个方法来退出一个Looper
,二者的区别是: quit
会直接退出Looper
,而quitSafely
只是设定一个标记,然后把消息队列中的已有消息处理完毕后才安全退出。这个好理解。
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
其实两个方法最终都是调用mQueue
的quit
方法。让我们来分析一下这个方法
// 最终都是调用到了这个方法
void quit(boolean safe) {
// 如果!mQuitAllowed为true即不能退出则抛出异常。
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
// 执行不同的退出逻辑方法
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// 唤醒MessageQueue
nativeWake(mPtr);
}
}
这个方法首先根据mQuitAllowed
参数判断是否能退出,如果可以退出则进入锁中执行退出逻辑。如果mQuitting==true
,那么这里会直接return
掉,而且mQuitting
这个变量只有在这里被执行了赋值,所以一旦looper
退出,则无法再次运行了。也就是说Looper
只能创建一次,一旦quit
之后此线程就无法再复活了。
- 而且同一个线程,只能创建一个
Looper
。因为Looper
的创建是通过Looper.prepare
方法实现的,而在prepare
方法中就判断了,当前线程是否存在Looper
对象,如果已经存在则抛出异常。所以同一个线程,只能创建一个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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
之后执行不同的退出逻辑,然后唤醒MessageQueue
,之后MessageQueue
的next
方法会退出,Looper
的loop
方法也会跟着退出,那么线程也就停止了。
(4)关于Looper的几个骚操作
①如何获取当前线程的looper
Looper.myLooper()
内部原理就是通过
ThreadLocal<Looper>
的sThreadLocal
对象的get
方法来获取Looper
sThreadLocal
对象在整个app中只有一个,因为它是由static final
修饰的
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
②如果在任意线程获取主线程的 Looper
Looper.getMainLooper()
③如何判断当前线程是不是主线程
方法一:
Looper.myLooper() == Looper.getMainLooper()
方法二:
Looper.getMainLooper().getThread() == Thread.currentThread()
方法三: 方法二的简化版
Looper.getMainLooper().isCurrentThread()
4.关于ThreadLocal
ThreadLocal
是Java
中一个用于线程内部存储数据的工具类,并不是Android所特有的。
值得注意的是,在不同的线程中访问同一个threadLocal
对象,但是它们获取的值却是不一样的,这就是ThreadLocal
的奇妙之处。那么问题来了,原理是什么?
我们进入其set
方法看一下
public void set(T value) {
//得到当前线程
Thread t = Thread.currentThread();
//得到当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//将当前的ThreadLocal变量作为key,传进来的泛型作为value进行存储
map.set(this, value);
else
//没有map则创建,并且将当前的ThreadLocal变量作为key,传进来的泛型作为value进行存储
createMap(t, value);
}
我们可以发现,每一个线程都维护着一个Map
集合,其名字为ThreadLocalMap
。我们可以通过getMap
方法来获得。此集合是以键值对的方式来存储数据的。键为ThreadLocal
对象,也就是调用set
方法的那个ThreadLocal
对象。这个值就是set()
里面传入的参数了。每一个线程都有一个ThreadLocalMap
。虽然键相同,但是取出来的值是不相同的。
- 我们可以这样类比,就是
ThreadLocal
是一个人,不同的线程即不同的Thread
和不同的ThreadLocalMap
,是不同的环境。相同的一个人(key
),在学校(学校的Thread
,里面有学校特有的ThreadLocalMap
)里面他的身份(value
)就是学生,在家庭里面他的身份(value
)可能是父亲,在社会上他的身份(value
)就是中国公民。即同一个人在不同的环境下有不同的身份,同一个ThreadLocal
在不同的线程下可能映射着不同的值,就是这个道理。 - 前面在
Looper
中提到的的ThreadLocal<Looper>
的sThreadLocal
,它的get
方法就是返回每个线程所特有的Looper
。其也是从ThreadLocalMap
中取值,虽然整个App中sThreadLocal
是唯一的,但是每个线程ThreadLocalMap
是不同的,所以不同的线程能得到不同的Looper
。
5.同步屏障
在Handler
机制中,有三种消息类型:
①同步消息。也就是普通的消息。
②异步消息。通过setAsynchronous(true)
设置的消息。
③同步屏障消息。通过postSyncBarrier
方法添加的消息,特点是target
为空,也就是没有对应的handler
。
同步屏障是什么意思?可以这么理解,比如救护车,在正常情况下,没有拉病人的情况下是可以和其他车一样在公路上正常行驶的,但是如果拉上病人就不一样了。不仅需要加急,不用等红灯,而且前面还有一辆警车帮忙带路。其实这个救护车就可以理解成异步消息,警车就可以理解成同步屏障消息。在后方有承载着病人的救护车的时候,其他车辆都会被警车“屏障”,救护车优先通行。这就是同步屏障机制存在的意义。当有要紧的异步消息需要立马被执行的时候,同步屏障消息就来了。具体过程我们可以看一下源码
Message next() {
...
for (;;) {
...
synchronized (this) {
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
...
}
...
}
}
我们可以看到,在next
方法,每次取出消息的时候,会先进行一个if
判断,即if (msg != null && msg.target == null)
,就是如果某个msg
不为null
而且它的target
为null
,那么它就是一个同步屏障消息
,那我们就要进入这个if
。此时会往后逐个遍历MessageQueue
中的消息,当发现下一个异步消息的时候,就退出循环,得到这个异步消息然后加急进行处理。
- 值得注意的是:同步屏障消息只是一个标志,就像我一开始说的警车一样,它上面并没有承载病人,真正承载病人的是后面的救护车,也就是有重要信息的异步消息。
- 而且并不是所有的异步消息都是需要加急处理的,正常情况下异步消息和同步消息都是可以根据
when
来决定处理顺序的,当然只是正常情况下,毕竟异步消息设计出来就是要被加急处理的,就像救护车的职能就是紧急救援病人的。
用张图表示就是这样
那么有什么加急消息呢?比如UI的更新,这就是一个加急消息。相信不用我解释大家都能理解咯。
6.HandlerThread
HandlerThread
是啥?看名字就知道,其实它继承了Thread
,本质上也是一个Thread
。但是它的run
函数中已经帮我们完成了Looper
的创建,不信你看
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
所以它第一个好处就是方便我们使用,也就是我们使用子线程创建Handler
的时候就不用再显示地创建Looper
了。
但是,光知道这个还是不够的,万一在HandlerThread
还没有创建好Looper
的时候就调用getLooper
方法怎么办呢?谷歌工程师早就想到了这个线程安全问题,所以我们看getLooper
方法
public Looper getLooper() {
...
// If the thread has been started, wait until the looper has been created.
//意思是如果线程已经启动了,那么等looper创建了之后再返回
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
从这个方法可以看出,人家getLooper
方法并不是无脑给你返回looper
,因为有可能还没有创建好,所以先wait
,等前面run
方法创建了looper
之后notifyAll
,这里getLooper
才给你返回looper
。这就是第二个好处,线程安全。
7.IntentService
首先看源码
public abstract class IntentService extends Service {
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
首先它是一个服务,可以借助子线程HandlerThread
,将消息一个接一个地执行,而且消息队列的特点是前面的消息执行完后面的消息才能执行,所以就可以让子任务在子线程中有条不紊地一个接一个地执行。而且消息执行完毕之后服务自动关闭stopSelf
。实现了内存释放。
我们的耗时任务的逻辑就是重写onHandleIntent
方法,在执行耗时任务的时候就会回调我们重写的onHandleIntent
方法。
简单来说,这就是一个可以在子线程进行耗时任务,并且在任务有序执行后自动停止的Service
8.Handler
内存泄漏
小题大做 | 内存泄漏简单问,你能答对吗(强烈建议大家学习此文)
参考文章
温故而知新 | 打破Handler问到底(这个强烈推荐大家学习)
你真的懂Handler吗?Handler问答
"一篇就够"系列: Handler消息机制完全解析