Android线程通信机制-Handler(Java层)
一、概述
Android的单线程UI模型,决定了在UI线程中不能进行耗时任务,在开发过程中,需要将网络、io等耗时任务放在工作线程中执行,工作线程中执行完成后需要在UI线程中进行刷新,因此就有了Handler进程内线程通信机制,当然Handler并不是只能用在UI线程与工作线程间的切换,Android中任何线程间通信都可以使用Handler机制。
Android的Handler机制应该说是有两套实现,Java层与native层分别实现了Handler机制,也就是说在Java层与native层各自维护了自己的消息队列,native层消息优先于Java层消息处理,在MessageQueue的源码中可以看到很多的native代码。这里只对Java层做个分析。
二、使用Handler实现线程间通信
1、在UI线程中使用Handler
UI线程中使用Handler非常简单,因为框架已经帮我们初始化好了Looper,只需要创建一个Handler对象即可,之后便可直接使用这个Handler实例向UI线程发送消息。
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// do something
}
};
注意:这种做法会导致内存泄漏。
我们通过Handler发送消息,在Message对象中会持有当前Handler对象的引用,在Java中非静态成员类、内部类、匿名类会持有外部对象的引用(这里在源码中有提到),而Looper是线程局部变量,其生命周期与UI线程相同,Looper持有MessageQueue的引用,MessageQueue持有Message的引用,当通过Handler发送一个延时消息未处理之前用户已经离开当前Activity,会导致Activity不能及时释放而内存泄漏。
解决思路:
既然知道是因为Handler持有Activity的引用而导致内存泄漏,那便让Activity在结束的时候不再有对象持有当前Activity的引用,或者不再有对象持有该Handler的引用,总之便是将这条引用链切断。
2、在非UI线程中使用Handler
在非UI线程中使用Handler一定要注意必须在创建Handler之前调用Looper.prepare()
方法来初始化Looper,否则会报异常。如果不调用Looper.loop()
方法,线程会在执行完毕后退出,也无法接收到消息。
private Handler handler;
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
// 这一步其实是创建threadA的线程本地变量Looper
Looper.prepare();
// 创建Handler
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Bundle bundle = msg.getData();
System.out.println(bundle.getString("msg"));
}
};
// 让threadA进入Looper循环中,不断的获取消息
Looper.loop();
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("<<<<<<<<<<<");
// 创建消息
Message msg = Message.obtain();
Bundle bundle = new Bundle();
bundle.putString("msg", "Hello World!");
msg.setData(bundle);
// 发送消息
handler.sendMessage(msg);
System.out.println(">>>>>>>>>>>");
}
});
这里只是为了简单写的示例代码,在实际开发中当然不会这么做,异步任务都会管理起来,不然离开了当前界面还有任务在执行(比如请求数据),那便没有意义了。
三、Handler机制实现原理
1、UML类图
Handler机制主要由Handler、Looper、MessageQueue、Message四个类组成,从下面的UML中可以看到Handler持有一个Lopper实例,这个Looper实例与线程相关,而Looper中管理着一个MessageQueue消息队列,MessageQueue本质上是一个链表。从UML类图大致能看到Handler的一个整体结构。
Handler、Looper、MessageQueue、Message类图2、Handler工作流程
2.1、基本流程
Handler工作流程主要分为两条支线,工作线程(也就是要发送消息的线程,后同)中发送消息实际上是将消息插入到消息队列MessageQueue中,初始化Handler的线程(即接受消息的线程,后同)则通过Looper.loop()
方法进入无限循坏,不断的从消息队列MessageQueue中取出消息,通过Message本身持有的Handler去分发消息。
2.2、线程切换的关键
1、在Looper初始化的时候,其实是在当前线程的本地变量(ThreadLocal)中存储了一个Looper,而Looper.loop()
在进入循环时,便是通过当前线程拿到Looper对象,从而拿到当前线程维持的MessageQueue消息队列,不断的读取消息。至于在另一个线程中通过Handler发送消息简单的说,读消息一直都是在初始化Handler的线程中进行,之后的分发消息与处理消息当然也是了。
2、在工作线程中发送消息,Handler本身持有了一个初始化线程的引用,当发送消息时,其实都是将消息放入初始化线程的消息队列中。
3、源码解析
3.1 Handler源码
从创建Handler开始看,Handler的构造方法有多个重载,最终都会走Handler(Callback callback, boolean async)
或者Handler(Looper looper, Callback callback, boolean async)
这两个构造方法。
public Handler(Callback callback, boolean async) {
// 检查是否是非静态成员类、内部类、匿名类,这几种会导致内存泄漏
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
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.myLooper()是从线程本地存储中拿到与当前线程相关的Looper
mLooper = Looper.myLooper();
// 这里抛出的异常就是我们经常看见的,未再非UI线程中使用Handler却没有调用Looperprepare()初始化Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// 接下来几行代码初始化一些成员变量
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Handler创建好了,接下来便是使用Handler,也就是通过Handler来发送消息。发送消息的方法主要有两大类,post****
系列发送一个Runnable
,作为处理消息的callback,或者send****
系列方法发送一个Message
,本质上都是构造一个Message
对象,加上消息分发的时间点,最终都会调用sendMessageAtTime(Message msg, long uptimeMillis)
这个方法。
有一个例外的方法,sendMessageAtFrontOfQueue(Message msg)
,默认将消息的执行时间点置为0,也就是立即分发,在入消息队列时会放置在队列头。
/**
* @param msg 要发送的消息
* @param uptimeMillis 消息的投递时间
* @return true 消息放入队列成功, false则是失败
*/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
// 这个方法很简单,在这里检查了下MessageQueue是否准备好了
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);
}
这个方法只有几行代码,真正入队列还是在MessageQueue中做的,却在入队列之前做了一件很重要的事情。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 重要的事情在这,设置了Message的target,这也是后面分发消息的关键
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 调用MessageQueue的成员方法将消息入队列
return queue.enqueueMessage(msg, uptimeMillis);
}
Handler
的另一个任务是分发消息并交给合适的方法去处理消息,由Handler.dispatchMessage(Message msg)
这个方法完成。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 这里的callback就是个Runable对象,会执行其run()方法
handleCallback(msg);
} else {
if (mCallback != null) {
// mCallback是在创建Handler对象时设置的监听
if (mCallback.handleMessage(msg)) {
return;
}
}
// 这个方法很熟悉了,就是创建Handler时复写的,也是最常用的
handleMessage(msg);
}
}
3.2 Looper源码
Looper
的代码非常少,先来看一下初始化方法,有两个重载Looper. prepare()
与Looper. prepare(boolean quitAllowed)
。
public static void prepare() {
// 调用的有参的那个构造方法
prepare(true);
}
/**
* @param quitAllowed 是否允许退出循环
*/
private static void prepare(boolean quitAllowed) {
// 检查是否已经初始化过了,从里可以看到`Looper.prepare()`在一个线程中只能调用一次
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 这里可以看到创建了一个Looper对象,并存入TLS
sThreadLocal.set(new Looper(quitAllowed));
}
接下来时Looper
的构造方法,这个构造方法是私有的,也就是说我们在初始化Looper时只能通过Looper.prepare()
这个方法。
private Looper(boolean quitAllowed) {
// 创建了消息队列,每个线程只有一个MessageQueue对象
mQueue = new MessageQueue(quitAllowed);
// 存储当前线程的引用
mThread = Thread.currentThread();
}
还有个prepareMainLooper()
方法,也是用来初始化Looper
的,这个方法只是为了UI线程使用,UI线程Looper
的初始化是在ActivityThread
的main()
方法中进行的。我们可以通过Looper.getMainLooper().getThread()
来获取UI线程。
接下来是Looper的消息循环方法Looper.loop()
,这个方法去掉一些安全性验证与Log,核心代码也很短。
public static void loop() {
// 获取当前线程绑定的Looper
final Looper me = myLooper();
// 未初始化Looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 当前线程的消息队列
final MessageQueue queue = me.mQueue;
...
for (;;) {
// 从消息队列中取出下一个消息,没有则阻塞
Message msg = queue.next(); // might block
// 取到null,这是因为MessageQueue已经退出消息循坏
if (msg == null) {
return;
}
// 分发消息,这里的target就是Handler对象,在Handler中消息入队列时设置的
msg.target.dispatchMessage(msg);
...
// 将Message放入消息池
msg.recycleUnchecked();
}
}
Looper
的任务就是创建一个循环器,不断的从消息队列取消息,交给Handler去分发。
3.3 MessageQueue源码
前面说Handler机制在native层与Java层都有实现,而Handler
与Looper
中都未出现native层的代码,其实是在MessageQueue
中将Java层与native层联系了起来,这里只分析Java层实现,在做应用开发的时候也往往只使用Java层的Handler机制。
首先来看一下MessageQueue
的构造方法,这个构造方法是包级权限,也就是说我们是无法在自定义的类中创建MessageQueue
这个类的实例的。
MessageQueue(boolean quitAllowed) {
// 是否允许退出消息循环
mQuitAllowed = quitAllowed;
// mPtr是native层消息队列的头指针
mPtr = nativeInit();
}
在分析Handler发送消息的时候,可以看到最终都是将消息Message
放入消息队列MessageQueue
中,MessageQueue称为消息队列,其实现的数据结构却并不是真正的队列,而是一个单链表,在插入消息节点Message
时按照时间点来确定位置。看下一下将消息入队列的MessageQueue.enqueueMessage(Message msg, long when)
方法。
boolean enqueueMessage(Message msg, long when) {
// 如果没有target,消息无法被处理
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.");
}
synchronized (this) {
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.markInUse();
// 设置消息被处理的时间点
msg.when = when;
Message p = mMessages;
boolean needWake;
// 消息队列中无消息,或消息的时间为立即执行,或消息的时间小于队列中第一个消息的时间,则将消息加入到队列头
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else { // 根据消息的时间将消息放入队列的合适位置,就是单链表的插入
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 唤醒线程
nativeWake(mPtr);
}
}
return true;
}
接下来看一下取出消息MessageQueue.next()
方法,这个方法挺长的,而且与native层交互很多。
简单说下大概的逻辑:
- 1.消息循环已经退出,则返回null,在
Looper.loop()
方法结束循环 - 2.阻塞操作,等待nextPollTimeoutMillis到达,或者线程被唤醒未到下一次唤醒时间,则阻塞线程,等待唤醒,在
MessageQueue.enqueueMessage(Message msg, long when)
方法中我们看到了唤醒的操作。 - 3.将取到的消息的时间与当前时间做比较,若还未到处理时间,则设置下一次轮询的超时时间
- 4.取出一条消息返回
- 5.消息队列为空,设置下一次超时时间为-1,会使线程一直阻塞,等待唤醒
Message next() {
// step 1
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// step 2
nativePollOnce(ptr, nextPollTimeoutMillis);
// 查找下一条消息,找到则返回
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 忽略所有的同步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) { // step 3
// 下一条消息执行时间还未到,设置一个超时时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else { // step 4
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
// 设置消息使用的标志位
msg.markInUse();
return msg;
}
} else {
// step 5
nextPollTimeoutMillis = -1;
}
// 执行退出消息,返回null
if (mQuitting) {
dispose();
return null;
}
// 消息队列为空或者消息未到执行时间,线程空闲,可以执行IdleHandlers
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 没有IdleHandlers执行,直接进入下一次循环继续等待消息
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
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,不会被再次执行
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
3.4 Message源码
Message
就是消息的实体类,也是消息队列MessageQueue
的节点,除了具有一些消息的基本属性,还持有下一条消息的指针。
Message
的构造方法是空的,其所有属性都是通过set方法来设置。在开发过程中,尽量使用obtain
系列的方法来获取一个消息实例,内部通过消息池来实现,减少因为创建对象而造成的开销,以达到复用的效果。
obtain
方法有许多重载,本质上都是调用无参的Message.obtain()
方法从消息池中取出一个消息实例,设置不同的属性。
public static Message obtain() {
// 通过同步sPoolSync对象,将sPool加锁,保证线程安全
synchronized (sPoolSync) {
if (sPool != null) { // 消息池不为空,则从消息池中取出一个消息实例
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // 清楚使用标志位
sPoolSize--; // 消息池数量减1
return m;
}
}
// 如果消息池为空,则创建一个新的消息对象
return new Message();
}
既然有消息池,那么消息池中的消息是哪来的呢,Message
中有个Message.recycle()
方法,这个方法便是将消息放入消息池中。
public void recycle() {
if (isInUse()) { // 正在使用的消息无法回收
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
// 真正的实现回收消息
recycleUnchecked();
}
Message.recycleUnchecked()
多了个uncheck,是真的不检查是否在使用,强行回收。
void recycleUnchecked() {
// 设置使用标志位
flags = FLAG_IN_USE;
// 清楚Message的所有属性
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++;
}
}
}
四、结束
大概记录了Handler机制在Java层的一个实现流程,从Handler发送消息,将消息加入到MessageQueue中,Looper不断的循环从MessageQueue中取出消息,将消息交给Handler处理。