多线程的消息传递处理,从初学Android时的Handler,懵懵懂懂地照猫画虎,到后来一头雾水的疑惑它为什么这么复杂,再到熟悉之后的叹为观止,一步步地都是自己踩过的足迹,都是成长啊哈哈哈。虽然离出神入化的境界还远十万八千里呢,但Android中的Handler多线程消息传递机制,的确是研发技术学习中不可多得的一个宝藏。本来我以为自己之前的学习以及比较了解 Handler,在印象中 Android 消息机制无非就是:
但是只知道整体流程,细节还不是特别透彻。最近不甚忙碌,回头看到这块又有些许收获,我们来记录一下吧。
在整个Android的源码世界里,有两大利剑,其一是Binder IPC机制,另一个便是消息机制。Android有大量的消息驱动方式来进行交互,比如Android的四剑客Activity
, Service
, Broadcast
, ContentProvider
的启动过程的交互,都离不开消息机制,Android某种意义上也可以说成是一个以消息驱动的系统。而Android 消息机制主要涉及 4 个类:
我们依次结合源码分析一下。
每个初学Android开发的都绕不开Handler这个“坎”,为什么说是个坎呢,首先这是Android架构的精髓之一,其次大部分人都是知其然却不知其所以然。所以决定再去翻翻源代码梳理一下Handler的实现机制。
我们都知道Android中主线程就是UI线程。在主线程不能做耗时操作,而子线程不能更新UI。主线程如果耗时操作太久(超过5秒)会引起ANR。子线程更新UI,会导致线程不安全,界面的刷新不能同步,可能不起作用甚至是崩溃。详细的分析可以看这篇文章Android子线程真的不能更新UI么?
上面这个规定应该是初学必知的,那要怎么来解决这个问题呢,这时候Handler
就出现在我们面前了,我们也可以利用AsyncTask
或者IntentService
进行异步的操作。这两者又是怎么做到的呢?其实,在AsyncTask和IntentService的内部亦使用了Handler
实现其主要功能。抛开这两者不谈,当我们打开Android源码的时候也随处可见Handler的身影。所以,Handler是Android异步操作的核心和精髓,它在众多领域发挥着极其重要甚至是不可替代的作用。我们先来一段经典常用代码(这里忽略内存泄露问题,我们后面再说):
首先在Activity中新建一个handler:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
mTestTV.setText("This is handleMessage"); //更新UI
break;
}
}
};
然后在子线程里发送消息:
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000); //在子线程有一段耗时操作,比如请求网络
mHandler.sendEmptyMessage(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
我们可以看到,子线程拿着主线程的mHandler
对象调用了它的sendEmptyMessage(0)
方法发送了一个空Message。然后主线程就更新了mTestTV
这个TextView的内容。下面,我们就根据这段代码逐步跟踪分析一下Handler源码,梳理一下Android的这个消息机制。
根据上面的Handler使用例子,我们从Handler的sendEmptyMessage()
方法这里开始,翻看Handler的源码:
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
我们可以看到,最后调用了sendMessageAtTime()
方法,我们接着看这个方法:
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;
}
//把msg对象入队
return enqueueMessage(queue, msg, uptimeMillis);
}
也就是说,目前我们看到的Handler的sendEmptyMessage()
方法调用逻辑如下图:
最后这个sendMessageAtTime()
方法我们看到两个亮点:
MessageQueue
类型的mQueue
对象。Message
类型的实例msg
对象入队。接下来,我们就沿着这两个问题分别往下跟踪。
我们先来看mQueue
这个MessageQueue对象哪来的呢?我们找到了赋值的地方,原来在Handler的构造函数里:
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());
}
}
mLooper = Looper.myLooper(); //使用Looper.myLooper()取到了mLooper对象
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //原来消息队列来自mLooper对象里的mQueue
mCallback = callback;
mAsynchronous = async;
}
原来mQueue
这个对象是从Looper
这个对象中获取的,同时我们看到是通过Looper.myLooper()
获取到Looper对象的。也就是说每个Looper拥有一个消息队列MessageQueue
对象。我们在Looper的构造函数里看到是它new了一个MessageQueue:
final MessageQueue mQueue;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); //初始化MessageQueue对象
mThread = Thread.currentThread();
}
我们紧接着再进入Looper类中的myLooper()
方法看看如何得到Looper实例对象的:
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
return sThreadLocal.get();
}
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal sThreadLocal = new ThreadLocal();
原来这个looper对象是从一个ThreadLocal
线程本地存储TLS对象中取到的,而且这个实例声明上面我们可以看到一行注释:如果不提前调用prepare()
方法的话sThreadLocal.get()
可能返回null。
我们来看看这个prepare()
方法到底干了什么:
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));
}
原来是给ThreadLocal
线程本地存储TLS对象set了一个新的Looper对象。换句话说,就是new了一个Looper对象然后保存在了线程本地存储区里了。而这个ThreadLocal
线程本地存储对象就是每个线程专有的变量,可以理解成线程的自有变量保存区。我们这里不作深入介绍,只用理解每个线程可以通过Looper.prepare()
方法new一个Looper对象保存起来,然后就可以拥有一个Looper了。这也就是我们在非UI线程中使用Handler之前必须首先调用Looper.prepare()
方法的根本原因。
插播:
ThreadLocal
类实现一个线程本地的存储,也就是说,每个线程都有自己的局部变量。所有线程都共享一个ThreadLocal对象,但是每个线程在访问这些变量的时候能得到不同的值,每个线程可以更改这些变量并且不会影响其他的线程,并且支持null值。详细介绍可以看看这里:Android线程管理之ThreadLocal理解及应用场景
比如我们在Activity的onCreate()方法中写一段这样的代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Handler h1 = new Handler();
new Thread(new Runnable() {
@Override public void run() {
Handler h2 = new Handler(); //直接在子线程中new一个Handler
}
}).start();
}
运行之后h1正常创建,但是创建h2的时候crash了:
——— beginning of crash
E/AndroidRuntime: FATAL EXCEPTION: Thread-263
Process: com.example.stone.sfsandroidclient, PID: 32286
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
at android.os.Handler.(Handler.java:200)
at android.os.Handler.(Handler.java:114)
at com.example.stone.sfsandroidclient.MainActivity$1.run(MainActivity.java:71)
at java.lang.Thread.run(Thread.java:818)
很明显,出错日志提示不能在一个没有调用过Looper.prepare()
的Thread里边new Handler()
。
看到了这里有一个疑惑,那就是我们在文章开头的示例代码中新建mHandler
的时候并没有调用Looper.prepare()
方法,那Looper的创建以及方法调用在哪里呢?其实这些东西Android本身已经帮我们做了,在程序入口ActivityThread的main方法里面我们可以找到:
public static void main(String[] args) {
...
Looper.prepareMainLooper(); //这里等同于Looper.prepare()
...
Looper.loop();
...
}
我们明白了MessageQueue消息队列对象是来自于ThreadLocal线程本地存储区存储的那个唯一的Looper对象。我们接着看Handler在发送消息的最后调用的enqueueMessage()
方法,看名字应该是把消息加入队列的意思,点进去看下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //注意此处Handler把自己this赋值给了Message的target变量
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
我们看到msg的target的赋值是Handler自己,也就是说这个msg
实例对象现在持有了主线程中mHandler
这个对象。注意这里,我们稍后会讲到msg
持有这个mHandler
对象的用途。最后调用了MessageQueue
类的enqueueMessage()
方法加入到了消息队列。
看来真正的入队方法交给了MessageQueue,这个enqueueMessage()
方法较长,我们现在继续进入看看:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) { //这里要求消息必须跟 Handler 关联
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()
方法,也就是出队方法。这个方法代码太长,可以不用细看我们知道它是用来把消息取出来的就行了。
Message next() {
//如果消息的 looper 退出,就退出这个方法
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 的这个方法
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
//获取下一个消息
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; //当前链表的头结点
if (msg != null && msg.target == null) {
//如果消息没有 target,那它就是一个屏障,需要一直往后遍历找到第一个异步的消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) { //如果这个消息还没到处理时间,就设置个时间过段时间再处理
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 消息是正常的、可以立即处理的
mBlocked = false;
//取出当前消息,链表头结点后移一位
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;
}
//如果收到退出的消息,并且所有等待处理的消息都处理完时,调用 Native 方法销毁队列
if (mQuitting) {
dispose();
return null;
}
//有消息等待过段时间执行时,pendingIdleHandlerCount 增加
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
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);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
可以看到,MessageQueue.next()
方法里有一个循环,在这个循环中遍历消息链表,找到下一个可以处理的、target
不为空的消息并且执行时间不在未来的消息,就返回,否则就继续往后找。
如果有阻塞(没有消息了或者只有 Delay 的消息),会把 mBlocked
这个变量标记为 true
,在下一个 Message 进队时会判断这个message
的位置,如果在队首就会调用nativeWake()
方法唤醒线程!
不过MessageQueue.next()
这个方法是在什么地方调用的呢,不是在Handler
中,我们找到了Looper
这个关键人物,专门负责从消息队列中拿消息。
我们又来到了Looper
的阵地,他在调用MessageQueue的next()
方法,来从消息队列中拿Message对象,关键代码如下:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) { //当前线程必须创建 Looper 才可以执行
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
//底层对 IPC 标识的处理,不用关心
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) { //无限循环模式
Message msg = queue.next(); //从消息队列中读取消息,可能会阻塞
if (msg == null) { //当消息队列中没有消息时就会返回,不过这只发生在 queue 退出的时候
return;
}
//...
try {
msg.target.dispatchMessage(msg); //调用消息关联的 Handler 处理消息
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//...
msg.recycleUnchecked(); //标记这个消息被回收
}
}
可以看到,Looper.loop()
也很简单,就是调用消息队列 MessageQueue.next()
方法取消息,如果没有消息的话会阻塞,直到有新的消息进入或者消息队列退出。也就是不断重复下面的操作,直到没有消息时退出循环
拿到消息后调用msg.target
的dispatchMessage(msg)
方法,而这个msg.target
是什么呢?就是前面Handler
发送消息sendMessageAtTime()
时把自己赋值给msg.target
的主线程的mHandler
对象。也就是说,最后还是 Handler 负责处理消息。可以看到,Looper 并没有执行消息,真正执行消息的还是添加消息到队列中的那个 Handler。
所以我们来看Handler中的dispatchMessage(msg)
方法:
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public void handleMessage(Message msg) {
}
可以看到,Handler 在处理消息时,会有三种情况:
Handler.postXXX(Runnable)
发送消息的时候会发生handleMessage()
方法Handler.handleMessage()
方法 而我们开头的例子,使用的就是第3种方法,大家可以回顾一下。
到这里,我们的疑问基本上就解决了,虽然没有再深入到jni层看native底层实现,但是java层的机制我们大概明白了。最后我们对上面的源码跟踪分析做一个宏观上的总结。
与Windows系统一样,Android也是消息驱动型的系统。引用一下消息驱动机制的四要素:
与之对应,Android中的实现对应了
也就是说,消息机制主要包含以下四个主角:
MessageQueue.enqueueMessage()
)和取走消息池的消息(MessageQueue.next()
);Handler.sendMessage()
)和处理相应消息事件(Handler.handleMessage()
);Looper.loop()
),按分发机制将消息分发给目标处理者。他们之间的关系如下:
一个Looper
类似一个消息泵。它本身是一个死循环,不断地从MessageQueue
中提取Message
或者Runnable。而Handler
可以看做是一个Looper
的暴露接口,向外部暴露一些事件,并暴露sendMessage()
和post()
函数。
在安卓中,除了UI线程
/主线程
以外,普通的线程(先不提HandlerThread
)是不自带Looper
的。想要通过UI线程与子线程通信需要在子线程内自己实现一个Looper
。开启Looper分三步走:
Looper
并Looper.prepare()
Looper.loop()
,线程进入阻塞态由于每一个线程内最多只可以有一个Looper
,所以一定要在Looper.prepare()
之前做好判定,否则会抛出java.lang.RuntimeException: Only one Looper may be created per thread
。为了获取Looper的信息可以使用两个方法:
Looper.myLooper()
获取当前线程绑定的Looper,如果没有返回null
。Looper.getMainLooper()
返回主线程的Looper
,这样就可以方便的与主线程通信。
Looper
调用prepare()
进行初始化,创建了一个与当前线程对应的Looper
对象(通过ThreadLocal
实现),并且初始化了一个与当前Looper
对应的MessageQueue
对象。Looper
调用静态方法loop()
开始消息循环,通过MessageQueue.next()
方法获取Message
对象。Message
对象时,让Message
的发送者(target
)去处理它。Message
对象包括数据,发送者(Handler
),可执行代码段(Runnable
)三个部分组成。Handler
可以在一个已经Looper.prepare()
的线程中初始化,如果线程没有初始化Looper
,创建Handler
对象会失败Handler
对象,它们都往同一个MQ中发消息,消息也只会分发给对应的Handler
处理。Handler
将消息发送到MQ中,Message
的target
域会引用自己的发送者,Looper
从MQ中取出来后,再交给发送这个Message
的Handler
去处理。Message
可以直接添加一个Runnable
对象,当这条消息被处理的时候,直接执行Runnable.run()
方法。再来看看我们的新建Handler的代码:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
...
}
};
当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有Activity的引用。
而Handler通常会伴随着一个耗时的后台线程一起出现,这个后台线程在任务执行完毕后发送消息去更新UI。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束。
另外,如果执行了Handler的postDelayed()方法,那么在设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。
解决方法之一,使用弱引用:
static class MyHandler extends Handler {
WeakReference mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}
从JDK1.2开始,Java把对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
强引用:我们一般使用的就是强引用,垃圾回收器一般都不会对其进行回收操作。当内存空间不足时Java虚拟机宁愿抛出OutOfMemoryError错误使程序异常终止,也不会回收具有强引用的对象。
软引用(SoftReference):如果一个对象具有软引用(SoftReference),在内存空间足够的时候GC不会回收它,如果内存空间不足了GC就会回收这些对象的内存空间。
弱引用(WeakReference) :如果一个对象具有弱引用(WeakReference),那么当GC线程扫描的过程中一旦发现某个对象只具有弱引用而不存在强引用时不管当前内存空间足够与否GC都会回收它的内存。由于垃圾回收器是一个优先级较低的线程,所以不一定会很快发现那些只具有弱引用的对象。为了防止内存溢出,在处理一些占用内存大而且生命周期较长的对象时候,可以尽量使用软引用和弱引用。
虚引用(PhantomReference) :虚引用(PhantomReference)与其他三种引用都不同,它并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。所以,虚引用主要用来跟踪对象被垃圾回收器回收的活动,在一般的开发中并不会使用它。
文章最后,我们来整理一下进程、线程间通信方式,参考线程通信与进程通信的区别。看看Handler消息传递机制属于哪种?
IPC | 数据拷贝次数 |
---|---|
共享内存 | 0 |
Android Binder | 1 |
Socket/管道/消息队列 | 2 |
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
很明显,Android的Handler消息机制使用消息队列( MessageQueue )实现的线程间通信方式。而Binder是Android建立额一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求。Binder基于Client-Server通信模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。此处就不对Binder作更多介绍了。