Android Handler机制解析
Handler
简介
定义:Android独有的一套消息传递机制
作用:在多线程场景中将工作线程要更新的UI操作信息传递到UI主线程中进行更新,避免线程操作不安全问题
核心角色:
源码梳理
Message
public final class Message implements Parcelable {
// Message类型
public int what;
public int arg1;
public int arg2;
public Object obj;
long when;
Handler target;
// 允许Message是可以执行callback的
Runnable callback;
// Message本身是队列
Message next;
// Message缓存相关,后续会介绍
public static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
}
Handler初始化
目前都推荐使用指定Looper形式创建Handler
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
在构造方法中,看到主要通过looper为handler进行了赋值操作,包括mQueue以及callback,值得注意的是Handler的mQueue是Looper的mq
Handler.sendMessage
核心:MessageQueue的入队操作
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
// 指定在多少毫秒时发送
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
// 省略mq为空抛异常情况
return enqueueMessage(queue, msg, uptimeMillis);
}
// MessageQueue
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
// 异步标志,后期需要注意此处
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
上述过程主要还是一个检查的过程,检查mQueue是否为空并指定在 X 毫秒时通过MQ入队该Message,接下来进入MessageQueue的enqueueMessage方法,即MessageQueue是如何把Message插入到自身队列中的。浏览如下代码时需要注意一个点即可明白,即:MessageQueue是通过Message的when(理想执行时间点)进行增序排序的
通常使用的handler.post(Runnable),其实也就是将Runnable包装成一个Message并通过sendMessage发送出去
MessageQueue.enqueueMessage
// MessageQueue
boolean enqueueMessage(Message msg, long when) {
// 检查发送该Message的Handler是否为空
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 检查该Message是否处于使用状态
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// 同步操作
synchronized (this) {
// Message被关闭,说明Thread可能被销毁了
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
// Message被回收
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
// 获取MQ中的当前队头的Message
Message p = mMessages;
boolean needWake;
// 当队列中为空 || message.理想执行时间为0 || message的理想执行时间<队头Message的理想执行时间,则头插法插入该Message
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
// 如果该队列被阻塞了,那应该设置标志在适当时间唤醒该队列
needWake = mBlocked;
} else {
// 按when排序插入消息到MessageQueue中
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 通过when将message插入到mq中正确位置
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
// 此处不是很理解
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
// 根据标志唤醒队列
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
现在我们知道了MessageQueue是如何插入一条Message的,那MessageQueue取出按时间线这条Message并执行呢?目前就只有Looper的作用还不清楚,联想其名字的含义:循环器,答案便呼之欲出了
Looper
创建
Looper中构造方法是私有的,创建Looper主要有两种static方式
- Looper.prepareMainLooper():为UI线程创建1个循环器对象
- Looper.prepare():为子线程(当前线程)创建1个循环器对象
这里先看prepare方法
// 这里为什么使用ThreadLocal存储?
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));
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private Looper(boolean quitAllowed) {
// 为Looper指定一个新的MessageQueue并指定Looper线程为当前线程
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
通常我们使用Looper.getMainLooper()使用即可,这里应该使用的主线程的Looper, 那它在何时进行调用呢?猜测应当是APP启动时就创建了
启动
// ActivityThread
public static void main(String[] args) {
Looper.prepareMainLooper();
Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
Looper.loop();
}
由此看到,在APP启动时,即创建了基于主线程的Looper对象,并通过loop方法让Looper跑起来,那loop方法应当就是Looper循环遍历的方法,这里看Looper是怎么处理的
轮询并处理
// Looper
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,此处可能阻塞
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
// Handler
public void dispatchMessage(Message msg) {
// 如果Message自己有Callback则调用它自己的
if (msg.callback != null) {
handleCallback(msg);
} else {
// 如果定义了Handler的callback,则调用
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 否则,最终调用自定义Handler中重写handleMessage
handleMessage(msg);
}
}
上述过程完成了Looper的循环已经对取出Message的处理操作,现在Handler机制中的最后一块拼图即是如何从MessageQueue中取出当前要执行的Message
MessageQueue.next取出Message
Message next() {
for (;;) {
int nextPollTimeoutMillis = 0;
// Naive方法,如果当前队列没有元素,则阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 如果存在Message但Message的Handler为空,则作废该Message
if (msg != null && msg.target == null) {
// 处理Handler异步消息逻辑,下文会分析
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
}-
// Message不为空且对应的Handler也不为空
if (msg != null) {
// 若当前时间小于Message理应执行时间,说明还没有到要执行队头Message的时间
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 当前时间大于Message理应执行时间,要执行该条Message了
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
// 队头Message出队
mMessages = msg.next;
}
// 取出的该条Message不再需要next指针
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
// Message即将执行,标记为正在使用
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
}
其他
异步消息
作用:即使该Handler被销毁,通过该Handler发送的Message也能够成功进行执行
我们平常使用的Handler发送的消息基本都是同步类型消息,可以通过message.setAsynchronous(true) 或者 在初始化Handler时设置async为true,代表该handler发送的消息默认为异步 消息。handler机制中对异步消息的处理逻辑在上文中MessageQueue的next()方法中
结合下文代码,此处我的理解是,异步消息就是就算该Handler被销毁,通过该Handler发送的Message也能够成功进行执行
// Handler
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
// 设置消息为异步消息类型
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
// MessageQueue.next()
if (msg != null && msg.target == null) {
// 处理Handler异步消息逻辑,下文会分析
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
同步屏障
实质:特殊Message,target为null,arg1为token
作用:在MQ取出消息时,屏蔽掉同步消息,优先获取异步消息
如果上,如果MQ中存在如下关系的Message队列,则取出消息的顺序是:
- 异步消息A
- 睡眠等待
此时队列变为:
也就是只要同步屏障存在,则其后的同步消息都会被屏蔽。
通过MessageQueue中的postSyncBarrier()添加消息屏障
// MessageQueue
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
// 设置token, 可根据token移除屏障
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
// 将该条Message按 when 插入到队列中
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;
}
return token;
}
}
处理逻辑还是在MessageQueue.next方法中
// MessageQueue
Message msg = mMessages;
if (msg != null && msg.target == null) {
//
do {
// 最终同步信息队头还是mMessages
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
那么若要移除指定token的barrier,则肯定是在队列通过Message的target与arg1特性找到该条Message,然后进行删除。但关键点是,删除后要唤醒MessageQueue。因为之前MQ可能因为该Barrier而处于阻塞状态。
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization " + " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
// 找到该条barrier Message
// 如果前面还有普通Message等待执行,则这个时候MQ肯定不需要被唤醒,因为此时唤不唤醒跟该Barrier没有关系
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
// 该Message就是队头,则看下一条Message是不是还是Barrier Message,如果是,则继续阻塞,如果不是且不为空,则要唤醒队列
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// 执行唤醒操作
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
Message的优雅使用
obtain
往往只要作为一个Callback的载体, 都多多少少存在着复用现象,Handler也不例外。对Message进行了缓存处理,通常使用obtain方法创建Message而尽量不new Message
// Message
public static final Object sPoolSync = new Object();
// 静态类型,所有Message只有一个sPool缓存
private static Message sPool;
private static int sPoolSize = 0;
// 限制最大缓存的空Message为50个
private static final int MAX_POOL_SIZE = 50;
public static Message obtain() {
synchronized (sPoolSync) {
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();
}
obtain流程
- 同步
- 如果Message的空对象缓存队列sPool有剩余元素,则使用该空的剩余元素(已经被使用过了的,但还没被回收的)
- 如果sPool中没有剩余元素,说明没有可用的空对象,则new一个Message
有将message从缓存池中取出的操作,就肯定有将message放入缓存池的操作,所以message使用后往往会调用recycle方法
recycle
public void recycle() {
// 检查标志
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
// 将Message属性重新初始化
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
// 该空Message可用,修改缓存池中数据
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
Handler内存泄露
原理
根源:Java中非静态内部类以及匿名内部类默认持有外部类的引用
当MQ中还有未被处理的消息,且持有该消息的handler是通过非静态内部类或匿名内部类创建时,基于Java Root GC机制,因为存在着上述引用链,导致创建该handler的Activity无法自动被GC,从而导致内存泄露
解决方案
1. 静态内部类+弱引用
静态内部类默认不持有外部类的引用,从而handler不再持有Activity的实例。同时,使用弱引用可确保在下次gc时,该activity一定能被回收
public class HandlerActivity extends AppCompatActivity {
private static class MyHandler extends Handler {
private final WeakReference<<HandlerActivity> mActivity;
public MyHandler(HandlerActivity activity) {
mActivity = new WeakReference<HandlerActivity>(activity);
}
}
2. Activity生命周期结束时清空名下handler消息队列
可在onDestroy时调用mHandler.removeCallbacksAndMessages(null)
@Override
protected void onDestroy() {
super.onDestroy();
if(handler!=null){
handler.removeCallbacksAndMessages(null);
}
}