在android开发中,我们经常会在子线程中通过handler发送数据给主线程,通知主线程做相应的操作。子线程、handler、主线程构成了线程模型中的经典问题:生产者-消费者模型:生产者和消费者在同一时间段内共用同一存储空间,生产者往存储空间中添加数据,消费者从存储空间中取走数据。
- 保证数据生产消费的顺序(通过MessageQuene,先进先出)
- 生产者和消费者之前没有耦合(子线程和主线程都只依赖于handler)
源码分析:
Handler机制相关类
Handler机制相关类 | 说明 |
---|---|
Handler | 发送和接受消息 |
Looper | 用于轮询消息队列,一个线程只能有一个Looper |
Message | 消息实体 |
MessageQueue | 消息队列,用于存储和管理消息 |
当我们调用handler.sendMessage()发送一个Message时,实际上是将这个Message发送到MessageQueue中,然后与当前线程绑定的Looper会不断地从MessageQueue中取出新的Message,调用msg.target.dispatchMessage(msg)将消息分发到handlerMessage()中。
1.实例化Handler
final Looper mLooper;
final MessageQueue mQueue;
public Handler(Callback callback, boolean async) {
//...
//检查当前的线程是否有Looper,不存在就抛异常(主线程在ActivityThread已为我们创建好了Looper)
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//Looper持有一个MessageQueueu
mQueue = mLooper.mQueue;
//初始化了回调接口
mCallback = callback;
mAsynchronous = async;
}
一个完整的Handler使用例子应该这样创建:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
//问题1:handler.handleMessage(msg)是在什么时候回调的呢?
public void handleMessage(Message msg) {
// 处理接收到的消息
}
};
Looper.loop();
}
}
2.Looper.prepare()
Looper提供了Looper.prepare()来创建Looper,借助ThreadLocal来实现与当前线程的绑定
static final ThreadLocal sThreadLocal = new ThreadLocal();
private static void prepare(boolean quitAllowed) {
//Android规定一个线程只能够拥有一个与自己关联的Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//在当前线程绑定一个Looper
sThreadLocal.set(new Looper(quitAllowed));
}
在Looper中,维持着一个Thread对象和MessageQueue对象
private Looper(boolean quitAllowed) {
//创建一个新的MessageQueue quitAllowed参数表示这个Queue是否能够被退出
mQueue = new MessageQueue(quitAllowed);
//将线程对象指向了创建Looper的线程
mThread = Thread.currentThread();
}
3.Looper.loop()
Looper.loop()会不断地从MessageQueue(MessageQueue.next())中获取Message,并调用msg.target.dispatchMessage(msg)回到Handler来分发消息。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// ...
for (;;) {
//不断地从MessageQueue获取消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// ...
try {
//问题2:Looper.loop()死循环中的msg.target是什么时候被赋值的呢?
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
//...
}
//...
//回收message
msg.recycleUnchecked();
}
}
msg.target.dispatchMessage(msg)中的msg.target就是发送该消息的Handler,这样就回调到了Handler那边去了
//Handler
public void dispatchMessage(Message msg) {
//msg.callback是Runable
if (msg.callback != null) {
handleCallback(msg);
} else {
//可以利用 Callback 这个拦截机制来拦截 Handler 的消息。
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//问题1:回调到Handler的handlerMessage方法
handleMessage(msg);
}
}
4.创建Message
可以直接new Message但有更好的方式Message.obtain。复用Message避免过多的创建、销毁Message对象达到优化内存的目的。
public static Message obtain(Handler h) {
Message m = obtain();//调用重载的obtain()方法
m.target = h;//绑定创建Message对象的handler
return m;
}
public static Message obtain() {
//sPoolSync是一个Object对象,用来保证线程安全。
synchronized (sPoolSync) {
//sPool是handler dispatchMessage后通过msg.recycleUnchecked()回收用来复用的Message
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
5.handler.sendMessage()
无论是handler.sendMessage(msg)还是handler.sendEmptyMessage(what),最终都追溯到以下方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//引用Handler中的MessageQueue
//这个MessageQueue就是创建Looper时被创建的MessageQueue
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
//将新来的Message加入到MessageQueue中
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//问题2:msg.target被赋值(即Message和Handler的绑定)的时机:在handler.sendMessage(msg)后,执行enqueueMessage的时候
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);//把消息设置为异步消息
}
return queue.enqueueMessage(msg, uptimeMillis);
}
其它问题:
1.Handler如何保证自身的线程安全?
消息入库(enqueueMessage) 和消息出库(next)加synchronized (this)
synchronized (this)这个锁,说明对所有调用同一个MessageQueue对象的线程来说,它们都是互斥的。在Handler里,一个线程对应一个唯一的Looper,而Looper中又只有唯一一个MessageQueue,所以主线程只有一个MessageQueue对象。所有子线程向主线程发送消息的时候,主线程一次都只会处理一个消息,其它消息需要等待,这样消息队列就不会出现混乱。同时也保证了enqueueMessage函数和next函数能够实现互斥,多线程访问的时候MessageQueue有序的进行。
2.消息机制之同步屏障
线程的消息都是放在同一个MessageQueue里面,取消息的时候是互斥取消息,而且只能从头部取消息,而添加消息时按照消息的执行的先后顺序进行的排序。如果一个消息需要立刻执行怎么办?所以需要给紧急需要执行的消息一个绿色通道,这个绿色通道就是同步屏障的概念。所谓同步屏障就是阻碍同步消息,让异步消息先执行。
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
synchronized (this) {
final int token = mNextBarrierToken++;
//1)没有个msg.target赋值,因此msg.target为null !!!!!
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
//2)将消息按照时间顺序插入到消息队列(单链表)中合适的位置
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
//3)返回一个序号,通过这个序号可以撤销屏障
return token;
}
}
Message对象初始化的时候没有给msg.target赋值,也就是一条msg.target==null
的消息进入了消息队列。那么开启同步屏障后,所有的异步消息是如何被处理的呢?
Message next() {
//...
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//1)如果有消息插入到消息队列或延时时间到,就会被唤醒,否则阻塞在这。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//2)如果target==null,那么它就是屏障,需要遍历,一直往后找到第一个异步消息
if (msg != null && msg.target == null) {
//3)遍历找异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
//如果有消息需要处理,先判断时间有没有到,没到的话设置一下阻塞时间
//场景如postDelay
if (now < msg.when) {
//计算出离执行时间还有多久赋值给nextPollTimeoutMillis
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
//链表操作,获取msg并删除该节点
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
//返回拿到的消息
return msg;
}
} else {
//没有消息就一直休眠,等待被唤醒
nextPollTimeoutMillis = -1;
}
//...
}
}
从上面可以看到,当消息队列开启同步屏障的时候,即msg.target==null
,消息机制在处理消息的时候会优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。
如上图所示:在队列中有同步消息、异步消息、屏障消息。有了同步屏障的存在,msg3这个异步消息可以被优先处理,后msg2这个同步消息不会被处理。我们只有调用removeSyncBarrier
移除这个同步屏障,msg2这个同步消息才能被处理。
同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的 setAsynchronous(true) 时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在 loop() 中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。
问题总结:
简述Handler原理
当我们调⽤handler.sendMessage(msg)发送⼀个Message时,实际上这个Message是发送到与当前线程绑定的的⼀个MessageQueue中,然后与当前线程绑定的Looper将会不断的从MessageQueue中取出Message,调⽤msg.target.dispatchMessage(msg)⽅法将消息分发到与Message绑定的handler.handleMessage()⽅法中。一个线程可以有几个Handler?几个Looper?
一个线程可以有多个Handler,只能有一个Looper。调用Looper.perpare会创建一个Looper保存在ThreadLocal中,每个线程只有一个ThreadLocalMap,会将Looper保存在对应线程中的LocalThreadLocalMap中,ThreadLocal为key,Looper为valueHandler内存泄漏问题及解决方案
非静态内部内持有外部类的引用,如果延时期间关闭了Activity,Message持有了Handler的引用,而Handler持有了Activity的引用,导致Activity无法被回收,导致内存泄漏。将Handler定义为静态内部类,在内部类中持有Activity的弱引⽤,并在onDestory前移除所有消息。可以在子线程中new Handler 吗?要怎么做?
可以通过子线程创建Handler,需要调用Looper.perpare和Looper.loop方法。子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
子线程中创建了Looper,当没有消息的时候子线程会被阻塞,无法被回收,所以我们需要手动调用quit方法将消息删除并唤醒looper,然后在next方法中返回null退出loop。既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它 内部是如何确保线程安全的?取消息呢?
在添加数据和取出数据的时候都加了this锁。一个线程对应一个唯一的Looper,而Looper中又只有唯一一个MessageQueue,所以主线程只有一个MessageQueue对象。所有子线程向主线程发送消息的时候,主线程一次都只会处理一个消息,其它消息需要等待,这样消息队列就不会出现混乱。同时也保证了enqueueMessage函数和next函数能够实现互斥,多线程访问的时候MessageQueue有序的进行。我们使用 Message 时应该如何创建它?
通过Message.obtain来创建Message。也就是复用之前的Message的内存,不会频繁的创建对象导致内存抖动。(享元设计模式就是重复利用内存空间,减少对象的创建)Looper死循环为什么不会导致应用卡死
卡死是ANR,指应用程序在一定时间内没有得到响应。loop循环是用于取出消息分发出去,没有消息会阻塞在next里的nativePollOnce方法里,并释放CPU资源进入休眠。Android中大多数操作都是通过Handler机制来完成的,包括ANR消息,如果没有消息,则不需要程序去响应,更不会有ANR。为什么系统不建议在子线程中更新UI
UI对象没有加锁,子线程中更新UI,会出现多线程安全问题。Handler是如何完成子线程和主线程通信的?
主线程创建Handler,在子线程中发送消息实际上是将消息放入到MessageQueue中,通过loop循环取出消息进行执行handleMessage,由于Looper是在主线程中初始化的,初始化的时候会创建消息队列,所以消息是在主线程被执行的。点击页面上的按钮后更新TextView的内容,谈谈你的理解?
点击按钮的时候会发送消息到Handler,但是为了保证优先执行,会加一个标记异步,同时会发送一个target为null的消息,这样在使用消息队列的next获取消息的时候,如果发现消息的target为null,那么会遍历消息队列将有异步标记的消息获取出来优先执行,执行完之后会将target为null的消息移除。(同步屏障)