前言
我们经常使用的Handler就是用来更新
UI
,我们知道,主线程不能执行耗时操作,这样会造成UI
卡顿,所以需要在子线程进行耗时的I/0操作,文件下载或者是访问网络。我们又知道,子线程不能更新UI
,当在完成子线程的耗时操作之后,就需要转换到主线程来更新UI
。那么,系统是怎么样切换线程,完成子线程到主线程的转换的?这就是本文要讲的重点,Android的消息机制。
Android的消息机制主要指的是Handler的运行机制,Handler是Android的一个上层接口,开发过程中只需要与Handler交互,通过它可以轻松地将一个任务切换到Handler所在的线程去执行。
Android为什么不允许在子线程更新UI
?
Android的UI
控件不是线程安全的,在多线程并发访问的条件下可能会导致UI
处于不可预期的状态。举个例子,假如允许子线程更新UI
,在子线程A对一个TextView
更新 为 “hello world”,在子线程B对这个TextView
更新为 “你好,世界 ”,线程并发环境下,就有可能出现 “你好,world” 的UI
界面,显然,这不是我们想要的。
那为什么系统不对UI
控件加上锁机制?
- 加上锁机制会让
UI
逻辑变复杂 - 锁机制会降低
UI
访问的效率
综上,最简单高效的方法就是采用单线程模型来处理UI
操作,即只允许在主线程更新UI
。
Handler的工作流程
Hander怎么样实现线程切换的?这是我们本文要分析的话题,要深入理解切换线程的原理,就得先知道Handler是怎么使用的。下面我通过一段代码,讲解Handler切换线程的流程,然后进行源码分析,理解原理实现。
//在主线程中创建Handler实例
//步骤2
private Handler handler = new mHandler();
//子线程
//步骤3
public void sonThread(){
new Thread(){
@Override
public void run() {
super.run();
// ....
// ....
// 在子线程中执行耗时操作
Message msg = Message.obtain();
msg.what = 1; //消息标识
msg.obj = "Message"; //消息内容
//步骤4
handler.sendMessage(msg); //发送消息
//耗时操作完成,需要更新UI,通过Handler发送消息
}
}.start();
}
//主线程
//步骤1
static class mHandler extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//步骤5
//收到消息。在此更新UI
// ....
// ....
}
}
Handler的使用分为4个步骤
- 自定义Handler子类(继承Handler类),复写
handleMessage()
方法 - 在主线中创建
Handler
实例 - 创建所需的消息对象
- 在子线程中发送消息到消息队列中
- 在
handleMessage
处理消息(更新UI
)
从代码层面上来看,大致可以分为这几个步骤,讲解了Handler的使用步骤。下面我们深入方法内部源码,看看这些步骤用了底层的哪些方法,从而掌握Handler底层实现的原理。
先说一下结论
子线程使用
Handler
发送消息到MessQueue
中入列,Looper
取出消息交给Hander
,主线程使用Handler
处理消息,更新UI
。下面我们根据这句话来分析具体的细节。

Handler
Handler
对象的创建
查看Handler
源码,发现Handler
共有七种构造方法

上面例子使用的是Handler()
,handler()
->handler(callback,async)
public Handler(@Nullable Callback callback, boolean async) {
...
...
//获取当前looper
mLooper = Looper.myLooper();
//如果当前线程没有looper,则说明没有调用looper.prepare(),抛出异常
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//把Looper的Queue赋值给Handle的Queue
mQueue = mLooper.mQueue;
//mCallBack赋值
mCallback = callback;
//mAsynchronous赋值
mAsynchronous = async;
}
Callback
:一个接口,内部有一个handleMessage
方法,当实例化Handler
时可以使用Callback
接口,避免自己去实现Handler
子类
async
:是否是异步消息,true
表示是异步消息,false
表示是同步消息,默认是同步消息。
异步消息相对于同步消息的而言的,表示消息不会受到中断或者事件的影响其全局顺序。异步消息是不受到
MessageQueue.enqueueSyncBarrier(long)
的同步障碍影响。
Handler
构造器完成了mQueue
、mCallBack
和mAsynchronous
的赋值,mQueue
是通过mLooper
获取的,一个线程只有一个Looper
,而mQueue
又是通过Looper
获取的,就样就实现了一个线程只有一个MessageQueue
。
Handler
发消息给MessageQueue
从发消息开始,进入sendMessage
方法内部,由sendMessage
->...->enqueueMessage
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
进入enqueueMessage
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
enqueueMessage
调用了MessageQueue
的enqueueMessage
的方法,这个方法很重要,这是Hander
与MessageQueue
关联的环节
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
return queue.enqueueMessage(msg, uptimeMillis);
现在来看看这个queue
是什么,依次向上查找,发现了这一行代码
MessageQueue queue = mQueue;
而mQueue
则是Handler
这个类中的一个成员变量
final MessageQueue mQueue;
Handler
发送消息的方法sendMessage
最终执行的是queue.enqueueMessage(msg, uptimeMillis)
,而queue
是一个MessageQueue
对象,所以可以这么理解,Handler
发送消息,实际上就是执行MessageQueue
的一个入队操作enqueueMessage
对应结合上面的例子,子线程使用
Handler
发送了一个Message
对象,handler.sendMessage(msg)
这行代码底层实现的就是MessageQueue
的enqueueMessage
入列操作,接下来看MessageQueue
的入列操作。
补充:MessageQueue
本质上是一个单链表结构,翻译过来就是一个消息队列,用于存放Handler
发送过来的消息,入列操作实际上就是链表的插入操作,我们可以通过源码来验证它。
Message
Message
是上述例子中所说的消息,当我们在子线程完成耗时操作,发送消息更新UI
。这里的消息指的就是Message
对象,Message
属性有
- what 用户定义的Message的标识符用以分辨消息的内容。
当我们在主线程执行
handleMessage
方法时,需要判断接收的是哪个消息以便执行,这个what
就是用来区分不同消息的属性。
arg1
和arg2
保存几个整形的数值obj
用来保存序列化的对象when
用于存储发送消息的时间点,以毫秒为单元
Message对象的创建
在上述例子中,有这么一段代码 Message msg = Message.obtain()
,它的作用是获取一个Message
实例,
为什么不是通过new
获取呢?
为了复用对象,可避免避免重复创建实例对象
,达到节约内存的目的。
public static Message obtain() {
// 保证线程安全
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
// flags为移除使用标志
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
spool
是一个Message
对象,默认是null
,当spool
为null
时,调用obtain
方法会重新new
一个Message
对象,既然官方不推荐使用new
获取对象,那么大部分情况下spool
是不为空的。

消息是怎么被放入消息对象池的?
查看recycleUnchecked
方法
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
// 添加正在使用标志位,其他情况就除掉
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
//拿到同步锁,以避免线程不安全
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
可以看出spool
在这里面也有赋值,当一个Message
被回收时,会把这个Message
对象作为消息对象池中下一个被复用的对象,并且将消息对象池的数量+1。也就是说,当系统回收消息的时候,会优先把他放入消息池,等待下一次复用。
MessageQueue
MessageQueue
对象的创建
看MessageQueue
的构造函数
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
MessageQueue
只是有一个构造函数,该构造函数是包内可见的,其内部就两行代码,分别是设置了MessageQueue
是否可以退出和native层代码的相关初始化
MessageQueue
的enqueueMessage
入列操作
//入队操作
boolean enqueueMessage(Message msg, long when) {
//判断target变量是否为null,能为null的只有障栅,而障栅入队则是通过postSyncBarrier()方法入队,所以msg的target一定有值,为null抛出异常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//加入同步锁
synchronized (this) {
//判断msg的标志位,因为此时的msg应该是要入队,意味着msg的标志位应该显示还未被使用。如果显示已使用,明显有问题,直接抛异常。
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//判断消息队列是否正在被关闭,如果正在被关闭,则入队失败,回收消息
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;
}
//修改msg的标记为,msg标记位显示已使用
msg.markInUse();
//设置msg的when
msg.when = when;
Message p = mMessages;
boolean needWake;
//p==null表示链表头部元素为null
//when==0表示立即执行
//when<p.when表示msg的执行时间早于链表中头部元素的时间
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
//设置msg成消息队列中链表头部元素
msg.next = p;
mMessages = msg;
needWake = mBlocked;
//如果上面三个条件都不满足则说明要把msg插入到中间的位置,不需要插入到头部
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
//needWake表示是否唤醒消息队列,如果头部元素不是障栅或者异步消息而且还是插入中间的位置,不需要唤醒消息队列
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//进入死循环
for (;;) {
//p指向的是mMessage,prev=p,在下一次循环时,prev指向了第一个message,以此类推
prev = p;
//移向下一个元素
p = p.next;
//p==null说明没有下一个元素,消息队列到头了,跳出循环
//when<p.when则说明当前入队的这个message的执行时间是小于队列中这个任务的执行时间的,
// 也就是说入队的这个message比队列中的message先执行,说明这个位置刚好是适合这个messgae的,跳出循环
//如果下面的两个条件都不满足,则说明这个位置还不是放置这个需要入队的message,则继续跟链表中后面的元素,】
// 也就是继续跟消息队列中的下一个消息进行对比,直到满足条件或者到达队列的末尾。
if (p == null || when < p.when) {
break;
}
//因为没有满足条件,说明队列中还有消息,不需要唤醒。
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//将入队的这个消息的next指向循环中获取到的应该排在这个消息之后的message。
msg.next = p; // invariant: p == prev.next
//将masg前面的message.next指向了msg
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
//如果需要唤醒,则唤醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
源码的详细注释已经在上面给出来了,现在我们只分析
enqueueMessage
所做的主要功能,就是Handler
在发送消息,执行sendMessage
,进而执行MessageQueue
的enqueueMessage
方法后,怎么实现在MessageQueue
队列中插入消息。
MessageQueue
翻译成队列,实际上它实现的是一个单链表结构,那为什么不使用数组结构?单链表的插入删除效率比数组高,而数组的查找效率比单链表高,
MessageQueue
就是利用了单链表插入删除效率高的优点,当我们使用Handler
发送消息的时候,实际上执行的是一个入列操作,而Looper
在处理收到的消息的时候,MessageQueue
实际上执行的是一个出列操作。Handler
机制中频繁使用的是MessageQueue
的入列出列操作,所以使用单链表结构来实现。
MessageQueue
的入列操作,实际上就是单链表的插入操作,当然,不仅仅是单链表插入一个数据这么简单,这里还需要执行MessaeQueue
这个消息队列一些特有的方法。
用一句话概括,enqueueMessage
方法就是遍历链表,按照when
属性的大小将插入的数据进行排序。
既然是单链表插入,那么插入消息msg
的条件是什么?或者说根据什么来确定插入的位置?
Message
中when
属性的作用:用于存储发送消息的时间点,以毫秒为单位
- 当消息队列中没有消息,将消息队列的头结点指针指向
msg
- 当消息
Message
的属性when
==0(表示立即执行),或者当前头节点的when
<此时插入消息的when
时,将消息msg
作为头结点插入在原有的消息前 - 不符合以上两点,遍历链表,找到符合
when
<当前遍历到的链表元素的when
的位置,插入在当前遍历到的当前链表元素的前面 - 若都不符合,插入在链表末尾
Looper
Hander
使用sendMessage
发送消息,sendMessage
又调用了MessageQueue
的enqueueMessage
方法,enqueueMessage
插入数据的原理就是链表的插入,在开头先说的结论中,是Looper
取出消息交给Handler
处理的,关于Looper
就会有下面的问题,
Looper
对象是怎么获取的 ?
查看Looper
的构造器,里面保存了一个MessageQueue
对象和一个Thread
对象,实现了Looper
与线程的关联。Looper
是线程私有的,每个线程都有一个Looper
对象。
并且构造器它是私有的,即Looper
对象不能直接创建
//Looper这个类的对象不能直接创建,必须通过Looper来的两个静态方法prepare()/prepareMainLooper()来间接创建
private Looper(boolean quitAllowed) {
//创建MessageQueue对象
mQueue = new MessageQueue(quitAllowed); //实现了Looper与MessageQueue的关联
//记录当前线程
mThread = Thread.currentThread(); //实现了Thread与Looper的关联
}
那么Looper对象是怎么创建的?查看prepare()
方法
public static void prepare() {
prepare(true);
}
实际调用的是prepare(true)
,查看prepare(true)
/**
*
* @param quitAllowed 表示Looper是否允许退出,true表示允许退出,fasle表示不允许退出
*/
private static void prepare(boolean quitAllowed) {
//每个线程只允许执行一次该方法,第二次执行的线程的TLS已有数据,则会抛出异常
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建Looper对象,并且保存到当前线程的TLS区域
sThreadLocal.set(new Looper(quitAllowed));
}
通过源码可以看出,Looper
对象的创建就是执行了 sThreadLocal.set(new Looper(quitAllowed))
这个语句,所以回到上面可以知道,Looper.prepare()
可以创建Looper
对象。并且保存在当前线程的TLS
区域,确保不同的线程拥有一个不同的Looper
对象,
上面分析中确保了不同线程有不同的Looper
对象,那么怎么保证同一个线程只能有一个Looper
对象?
代码中执行
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
判断当前线程中是否已有Looper
对象,如果有,还企图新建Looper
对象的话,就会抛出一个"Only one Looper may be created per thread"异常,表示一个线程只能拥有一个Looper
对象,这样就可以保证在正常运行的情况下,一个线程只有一个Looper
对象。
主线程也是通过这种方法获取Looper
对象吗?
Android
主线程ActivityThread
在启动时,会在入口方法main
中通过Looper.prepareMainLooper()
来创建主线程的Looper对象以及MessageQueue
,这也是我们可以在主线程没使用Looper.prepare
而能使用Handler
传递消息的原因。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
...
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();//通过此方法创建Looper对象
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
...
}
ActivityThread
通过调用Looper.MainLooper()
来创建Looper
对象,查看Looper.MainLooper()
@Deprecated
public static void prepareMainLooper() {
//设置不允许退出的Looper
prepare(false);
synchronized (Looper.class) {
//将当前的Looper保存为looper,每个线程只允许执行一次
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper(); //给主线程的looper赋值
}
}
标记为废弃的原因:主线程的Looper是由系统自动创建的,无需用户自行调用。
Looper
怎么与MessageQueue
实现关联?
使用Handler
,Handler
的构造函数中,Handler
里面MessageQueue
就是Looper
的MessageQueue
,Handler
默认采用的是当前线程TLS
中的Looper对象,只要执行了Looper.prepare()
方法,就可以获取到有效的Looper
对象,同时又执行了 mQueue = mLooper.mQueue
语句,把当前Handler
的中MessageQueue
对象mQueue
的引用指向了Looper
对象mLooper
中MessageQueue
对象mQueue
的引用,即当前Hander
中,MessageQueue
对象就是Looper
中MessageQueue
中的对象。
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
//如果是匿名类、内部类、方法内部类,且没有声明为static,则存在内存泄漏风险,要打印日志
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
//获取当前looper
mLooper = Looper.myLooper();
//如果当前线程没有looper,则说明没有调用looper.prepare(),抛出异常
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//把Looper的Queue赋值给Handle的Queue
mQueue = mLooper.mQueue;
//mCallBack赋值
mCallback = callback;
//mAsynchronous赋值
mAsynchronous = async;
}
Looper
是怎么取出消息的?
在子线程使用Handler
发送消息,消息插入主线程的MessageQueue
消息队列中,Looper
又在消息队列中取出消息,分发给主线程处理。在这个过程中,主线程里面的MessageQueue
和Looper
是怎么实现关联的?看看Looper
的looper
方法
public static void loop() {
//第1步
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//第2步
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//第3步
for (;;) {
//第四步
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
// 第5步
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
// 第6步
msg.recycleUnchecked();
}
}
looper
方法内的具体操作有:
第1步 获取
Looper
对象第2步 获取
MessageQueue
消息队列对象第3步
while()
死循环遍历第4步 通过
queue.next()
来从MessageQueue
的消息队列中获取一个Message msg
对象第5步 通过
msg.target. dispatchMessage(msg)
来处理消息第6步 通过
msg.recycleUnchecked()
方来回收Message到消息对象池中
分析第四步,进入MessageQueue
的next
方法,这是整个handler
机制的核心方法
Message next() {
// 如果消息循环已经退出了。则直接在这里return。因为调用disposed()方法后mPtr=0
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//记录空闲时处理的IdlerHandler的数量
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// native层用到的变量 ,如果消息尚未到达处理时间,则表示为距离该消息处理事件的总时长,
// 表明Native Looper只需要block到消息需要处理的时间就行了。 所以nextPollTimeoutMillis>0表示还有消息待处理
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
//刷新下Binder命令,一般在阻塞前调用
Binder.flushPendingCommands();
}
// 调用native层进行消息标示,nextPollTimeoutMillis 为0立即返回,为-1则阻塞等待。
nativePollOnce(ptr, nextPollTimeoutMillis);
//加上同步锁
synchronized (this) {
// 获取开机到现在的时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// 获取MessageQueue的链表表头的第一个元素
Message msg = mMessages;
// 判断Message是否是障栅,如果是则执行循环,拦截所有同步消息,直到取到第一个异步消息为止
if (msg != null && msg.target == null) {
// 如果能进入这个if,则表面MessageQueue的第一个元素就是障栅(barrier)
// 循环遍历出第一个异步消息,这段代码可以看出障栅会拦截所有同步消息
do {
prevMsg = msg;
msg = msg.next;
//如果msg==null或者msg是异步消息则退出循环,msg==null则意味着已经循环结束
} while (msg != null && !msg.isAsynchronous());
}
// 判断是否有可执行的Message
if (msg != null) {
// 判断该Mesage是否到了被执行的时间。
if (now < msg.when) {
// 当Message还没有到被执行时间的时候,记录下一次要执行的Message的时间点
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Message的被执行时间已到
// 从队列中取出该Message,并重新构建原来队列的链接
// 此时说明说有消息,所以不能阻塞
mBlocked = false;
// 如果还有上一个元素
if (prevMsg != null) {
//上一个元素的next(越过自己)直接指向下一个元素
prevMsg.next = msg.next;
} else {
//如果没有上一个元素,则说明是消息队列中的头元素,直接让第二个元素变成头元素
mMessages = msg.next;
}
// 因为要取出msg,所以msg的next不能指向链表的任何元素,所以next要置为null
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
// 标记该Message为正处于使用状态,然后返回Message
msg.markInUse();
return msg;
}
} else {
// 没有任何可执行的Message,重置时间
nextPollTimeoutMillis = -1;
}
// 关闭消息队列,返回null,通知Looper停止循环
if (mQuitting) {
dispose();
return null;
}
// 当第一次循环的时候才会在空闲的时候去执行IdleHandler,从代码可以看出所谓的空闲状态
// 指的就是当队列中没有任何可执行的Message,这里的可执行有两要求,
// 即该Message不会被障栅拦截,且Message.when到达了执行时间点
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 这里是消息队列阻塞( 死循环) 的重点,消息队列在阻塞的标示是消息队列中没有任何消息,
// 并且所有的 IdleHandler 都已经执行过一次了
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
// 初始化要被执行的IdleHandler,最少4个
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 开始循环执行所有的IdleHandler,并且根据返回值判断是否保留IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 重点代码,IdleHandler只会在消息队列阻塞之前执行一次,执行之后改标示设置为0,
// 之后就不会再执行,一直到下一次调用MessageQueue.next() 方法。
pendingIdleHandlerCount = 0;
// 当执行了IdleHandler 的 处理之后,会消耗一段时间,这时候消息队列里的可能有消息已经到达
// 可执行时间,所以重置该变量回去重新检查消息队列。
nextPollTimeoutMillis = 0;
}
}
在MessageQueue
的next
中,执行各种语句的最终目的都是返回一个Message
对象,这个Message
可能为空
接下来分析一下,
-
MessageQueue
会先判断队列中是否有障栅存在,如果有,只会返回异步消息(同步屏障),如果没有,就逐个返回 - 当
MessageQueue
没有任何消息可以处理的时候,它会进入阻塞状态等待消息到来(无限循环),在阻塞之前它会执行一遍IdleHandler
,阻塞其实就是不断循环查看是否有先消息进入队列 - 当
MessageQueue
被关闭的时候,成员变量mQuitting
会被标记为true
,然后Looper
尝试从消息队列中取出Message
的时候返回null
,而Message==null
就是通知Looper
消息队列已经关闭,应该停止循环。

-
Handler
里面有两个无限循环体Looper
和MessageQueue
,真正阻塞的地方是MessageQuue
的next
方法
障栅:障栅指的就是同步屏障,前面在
Handler
构造器中说过Message
分为同步消息和异步消息,障栅实际上也是一个Message
对象,target=null
是它与其他Message
对象的区别,next
方法当中,如果设置了障栅,开启同步屏障(MessageQueue#postSyncBarrier()
)则会优先处理异步消息,其他同步消息需要移除障栅(removeSyncBarrier()
)才能处理。
IdleHandler
:IdleHandler
在处理业务逻辑方面和Handler
一样,不过它只会在线程空闲的时候才执行业务逻辑的处理,这些业务经常是哪些不是很紧要或者不可预期的,比如GC
Handler
是怎么处理消息的?
Handler
发送消息 ---> MessageQueue
插入消息 ---> Looper
接收消息 ---> Handler
处理消息
Looper
通过MessageQueue
的queue.next
()拣出消息后,调用msg.target.dispatchMessage**(**msg**)**
把消息分发给对应的Handler
,跟到:Handler
-> dispatchMessage
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//当Message存在回调方法,回调msg.callback.run()方法;
handleCallback(msg);
} else {
if (mCallback != null) {
//当Handler存在Callback成员变量时,回调方法handleMessage();
if (mCallback.handleMessage(msg)) {
return;
}
}
//Handler自身的回调方法handleMessage()
handleMessage(msg);
}
}
- 如果
msg.callback
不为空,则执行handleCallback(Message)
,而handleCallback(Message)
的内部最终调用的是message.callback.run()
;,所以最终是msg.callback.run()
。
- 如果
msg.callback
为空,且mCallback
不为空,则执行mCallback.handleMessage(msg)
。
- 如果
msg.callback
为空,且mCallback
也为空,则执行handleMessage()
方法
开头的例子就是调用到handleMessage
方法,至此,例子中的消息传递分析完毕。一开始分析了Handler
的创建,又分析了Handler
发送消息sendMessage
方法,再分析了MessageQueue
的enqueueMessage
方法,由enqueueMessage
方法分析到了Looper
中的looper
,期间也分析了MessageQueue
和Looper
对象的获取,最后分析了Handler
的dispatchMessage
方法。
从消息的发送到消息的处理,经历了Handler
-->MessageQueue
-->Looper
->Handler
分析完毕。
小结
Looper在主线程中死循环,为啥不会ANR
?
Looper
通过MessageQueue
的next
获取消息队列消息当队列为空,会阻塞。此时主线程也堵塞在这里,好处是:main函数无法退出,APP不会一启动就结束!
主线程阻塞怎么响应用户操作?
application启动时,不止一个main线程,还有其他两个
Binder
线程:ApplicationThread
和 **ActivityManagerProxy
,用来和系统进程进行通信操作,接收系统进程发送的通知。当系统受到因用户操作产生的通知时,会通过 Binder 方式跨进程通知
ApplicationThread
;它通过Handler机制,往
ActivityThread
的MessageQueue
中插入消息,唤醒了主线程;
queue.next()
能拿到消息了,然后dispatchMessage
完成事件分发;死循环不会
ANR
,但是dispatchMessage
中又可能会ANR
!如果你在此执行一些耗时操作,导致这个消息一直没处理完,后面又接收到了很多消息,堆积太多,就会引起ANR
异常。
Handler为什么会发生内存泄漏,怎么解决?
在Java中,非静态内部类会持有一个外部类的隐式引用,可能会造成外部类无法被
GC
; 当Handler
内部类没有声明为静态时,它会持有Activity的引用从而导致Activity无法正常释放。而单单使用静态内部类,Handler就不能调用Activity里的非静态方法了,所以加上「弱引用」持有外部Activity。
还有一种情况可能会引起内存泄漏:延时消息,Activity关闭消息还没处理完,可以在Activity的onDestroy()
中调用:handler.removeCallbacksAndMessages(null)
移除Message/Runnable
。
Hanlder
怎么处理加急信息,类型UI
绘制等消息?
使用同步屏障机制,同步屏障机制可以优先处理异步消息。在
MessageQueue
的next
代码中,会优先判断是否设置了同步屏障,如果设置了同步屏障(通过MessageQueue
的postSyncBarrier
函数来开启同步屏障)会不断循环消息队列直到找到异步消息并进行处理,类似UI
绘制这种需要快速响应的消息,系统会优先执行。在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞。如果想恢复处理同步消息,需要调用removeSyncBarrier()
移除同步屏障。
子线程维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
主线程无消息的时候,休眠阻塞 子线程中必须要调用
Looper.quit()
退出loop
是一个死循环的操作,要让loop
退出,需要msg==null,这个时候就需要返回一个空的消息quit()
的作用 唤醒线程 将mquitting=true
,让messagequeuenue==ll
,退出loop()
发消息时各个Handler可能处于不同线程,那么它内部是如何保证线程安全的?取消息呢?
加锁
enqueueMessage
方法中使用synchronized(this)
同步锁(内置锁),锁定了该对象
线程是如何进行切换的?
调用
Looper
的loop()
方法无限循环查询MessageQueue
队列是否有消息保存了。有消息就取出来调用dispatchMessage()
方法处理。 这个方法最终调用了我们自己重写了消息处理方法handleMessage(msg)
;这样就完成消息从子线程到主线程的切换
Handler用了什么设计模式?用它的好处是什么?
生产者-消费者模式 内存共享的设计 好处:可以保证数据生产消费的顺序(
MessageQueue
先进先出)不管是生产者(子线程)还是消费者(主线程)都只依赖缓冲区(Handler
),不会相互持有,没有任何耦合
最后
我这里总结了一些BAT大厂关于Handler 的超过 100+ 高频面试题,现已经整理成了高清的PDF学习文档,需要的朋友可以直接去我 GitHub地址:https://github.com/733gh/Android-T3 中查阅;基本涵盖了各个角度,大家可以拿来自测一下。在面试前也可以刷一刷,毕竟 Handler 面试题虽高频出现,但是遇到还是不用慌张的。

网友评论