安卓系统是消息驱动的,所以深刻了解Handler整个消息分发机制,对于我们了解安卓系统,是一个必不可少的知识点。整个过程中,我们需要重点关注的类是:
1. Handler
2. Looper
3. MessageQueue
4. Meesage
5. ThreadLocal
6. Thread
这几个类之间的联系:Handler发送消息和接收消息都是通过Message,而基于链表的Message是由MessageQueue管理的,Looper用于阻塞和唤醒线程以便处理Message,为确保每个线程中顶多只有一个Looper对象,Android使用ThreadLocal来管理Looper,ThreadLocal并不是Thread的子类,它只是用ThreadLocal.ThreadLocalMap来存储Looper,最终Looper对象被存储在Thread对象中的threadLocals变量中。
本博客所分析的handler消息分发机制是基于andorid-28这个sdk来的,之所以要强调,是因为我发现ThreadLocal这个类,在android-23和android-28中不太一样,android-23中用来存储数据的类是ThreadLocal.Value,而android-28中是ThreadLocal.ThreadLocalMap,看了两者代码,其实差不多。不知道其他类有没有其他的区别,这里特此强调一下。
先来看看我们使用handler最常用的用法,一般在子线程做耗时操作,然后用handler来通知主线程进行UI更新啥的:
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
Log.d("test","接收到消息了。。");
break;
}
}
};
private void operate(){
new Thread(new Runnable() {
@Override
public void run() {
try {
Log.d("test", "模拟耗时操作...");
Thread.sleep(5000);
Log.d("test", "子线程耗时操作结束,准备通知主线程...");
handler.sendEmptyMessageDelayed(1, 5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
打印的log如下:
09-16 13:55:14.923 24522-24756/com.rxjavatest D/test: 模拟耗时操作...
09-16 13:55:19.923 24522-24756/com.rxjavatest D/test: 子线程耗时操作结束,准备通知主线程...
09-16 13:55:24.929 24522-24522/com.rxjavatest D/test: 接收到消息了。。
接下来,我们分析handler.sendEmptyMessageDelayed()
到底干了什么:
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
可以看到,虽然我们没传Message对象进去,但是sendEmptyMessageDelayed()方法里面会通过Message.obtain()获取一个复用的Message对象,然后将延时时间赋值到此msg对象中。刚开始我也不理解这里为什么要有一个Message对象,后面看了MessageQueue才得知,handler发送消息是基于Message的,每个消息事件,必须得由Message对象来传递。
然后,一路顺藤摸瓜跟踪后面的几个方法:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
可以看到,关于发送消息的延时的时间计算,它是由我们传递进去的delayMillis,加上SystemClock.uptimeMillis(),SystemClock.uptimeMillis()获取的时间是从开机到当前所占用的时间,如果手机进入深度睡眠(比如关闭屏幕有可能进入深度睡眠),SystemClock.uptimeMillis()获取的时间又是从0开始的了。大家可以试一下,用SystemClock.uptimeMillis()获取的时间,一般是小于手机设置中看到的开机时间的。
然后,继续往下看:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这里就将消息加入到了MessageQueue的enqueueMessage()里面。handler发送消息暂告一段落,后面分析MessageQueue再往下细说。
咱回过头来看看handler中的handleMessage()是如何回调的。handler的回调支持两种匿名内部类的写法,第一种是继承Handler的,第二种是实现Handler.Callback接口的,很显然,第一种简洁多了:
new Handler(){
@Override
public void handleMessage(Message msg) {
}
};
new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
两者都是在dispatchMessage()中处理的: :
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里的msg.callback只有使用handler.post(Runnable r)时,才会对smg.callback赋值,目前只能是null,虽然Message中有一个setCallback(),但它是一个隐藏的方法,估计是还没设计好吧。else里面逻辑也很简单,如果callback对象不为空,走callback的回调,否则就走继承handler的回调。那么dispatchMessage()又是在哪里调用的呢:
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
上面这段代码是Looper.loop()
中死循环里的,在Looper.loop()里,通过MessageQueue.next()
阻塞,一有消息就获取到,并通过msg.target.dispatchMessage(msg)
将消息回调给Handler所在线程的handleMessage()
,这就是Handler的消息回调机制。
我们接下来看看handler.removeCallbacksAndMessages()
做了什么:
/**
* Remove any pending posts of callbacks and sent messages whose
* obj is token. If token is null,
* all callbacks and messages will be removed.
*/
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
handler中删除所有回调和消息,最终还是删除MessageQueue中的回调和消息,当token为null时,表示删除所有。我们接着看MessageQueue中删除回调和消息是怎么做的:
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
**Message是基于链表结构的,所以通过Message.next()不断循环回收掉message对象。**看到这里各种实例对象的命名,n/nn/h/p,google工程师也偷懒了?。接下来再看下Message.recycleUnchecked()
做了什么事情:
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
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++;
}
}
}
将Message对象中各种属性都置空,就表示清除了此Message对象~
相同点:两者都是在当前线程事件处理完后,回调到new了Handler的那个线程里面,再去做逻辑处理。
不同点:handler.sendMessage()会将处理的这个message对象传过去,算是一个优化吧。
消息队列中我们重点看两个方法,消息加入队列enqueueMessage()和取消息队列next()。
boolean enqueueMessage(Message msg, long when) {
......
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) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} 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 = 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;
}
大家重点看一下for (; ; ){}这个死循环。Message是一个链表数据的结构,当有一个新的msg对象到来,这个msg插入到原始msg里面会有两种可能,一种是插到最后面,一种是插到中间,而不管是哪一种,都是根据msg.when
这个时间参数来的,也就是我们在handler中提到的SystemColock.upTimeMillis()+delaysTime
这两个时间。
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
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();
}
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;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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 {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
......
}
}
可以看到,还是在for(; ; ){}这个死循环里,会根据msg.when去取mMessages
这个公共变量值中时间刚好到了的msg对象。
Looper最开始出现的地方,是我们在new一个handler对象时:
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
在handler的构造方法中,会去获取handler所在线程的looper实例,以及looper实例中的消息队列实例。这里我们也能看到,如果mLooper==null
,会抛异常。所以,我们在new一个handler时,需要先在当前线程调用Looper.prepare()
初始化Looper。
那有人就会问了,为啥我们在主线程不初始化Looper实例,就能随意new Handler呢?这是因为,在ActivityThread.java
这个类中,已经调用了Looper.prepareMainLooper()
了:
public static void main(String[] args) {
.......
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
.......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
ActivityThread
中的main()方法,是应用程序的主入口。从这里我们可以看到,深刻理解handler
和looper
机制,对于理解以消息驱动的安卓系统来说,太特么重要了。首先初始化了主线程的Looper实例,然后通过Looper.loop()让主线程阻塞,最后一个抛异常的语句也说明了,主线程必须是阻塞的,否则就会抛异常。
所以,如果在主线程调用以下方法,妥妥地让你crash:
Looper.getMainLooper().quitSafely();
有时候,我们想在子线程使用Handler进行消息分发,使用情况如下:
new Thread(
new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler mHandler = new Handler();
Looper.loop();
}
}
).start();
当然了,如果我们在子线程中使用了handler,当退出子线程时,我们必须手动调用Looper.myLooper().quitSafely();
或者Looper.myLooper().quit();
否则子线程就会由于Looper一直处于阻塞状态,而导致内存泄漏问题。
上面一直在说阻塞,和handler接收消息,那阻塞和唤醒的原理又是什么样的呢?我们来看下Looper.loop():
/**
* 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) {
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
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
msg.recycleUnchecked();
}
}
在for(; ; ){}
这个死循环里,其实导致线程阻塞的,是MessageQueue
对象中的next()
,当取出了消息,就会通过msg.target.dispatchMessage(msg)
将消息分发出去,最终走到handleMessage(msg)
。接下来再看看MessageQueue
中的next()
方法:
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
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();
}
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;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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 {
// No more messages.
nextPollTimeoutMillis = -1;
}
......
}
在for(; ; ){}
这个死循环当中,主要有两点会进入阻塞状态,一是当消息队列中没有消息时,它会使线程进入等待状态;二是消息队列中有消息,但是消息指定了执行的时间,而现在还没有到这个时间,线程也会进入等待状态。而nativePollOnce()
是一个native方法,它跟nativeWake()
相对应使用,一个用于阻塞线程,一个用于唤醒线程,而它们底层的通信机制,其实是属于linux通信中的管道通信机制。而管道通信的特点是:进程试图读一个空管道时,在数据写入管道前,进程将一直阻塞,进程再试图写管道,在其它进程从管道中读走数据之前,写进程将一直阻塞。MessageQueue的插入消息和取出消息,分别对应管道通信的写数据和读数据。
ThreadLocal翻译过来叫“本地线程”,其实它并不继承于Thread,它的作用,是为了保证一个线程,最多只能有一个类的实例对象。使用场景的话,就是在多线程的环境中,我们为了保证每一个线程最多只能有一个类的实例对象,这时候用ThreadLocal来保存此对象,是再适合不过的了。在安卓中,handler消息驱动机制规定每个线程只能有一个Looper来管理消息的阻塞和唤醒,所以,google工程师们就用了ThreadLocal来管理Looper。
那ThreadLocal存储的原理又是怎样的呢?首先,我们来看看Looper类中的的一个重要的对象:
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal sThreadLocal = new ThreadLocal();
由此可以看到,sThreadLocal对象不属于Looper类,而属于静态静态方法区。在实例化Looper对象前,会对当前线程中是否有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));
}
sThreadLocal.get()
获取的就是当前线程的looper对象,如果我们在当前线程调用过Looper.prepare()方法了,再次调用时,sThreadLocal.get()
不为空,就会抛异常。这里的sThreadLocal.get()
和sThreadLocal.set()
,就是ThreadLocal最重要的两个方法了。
我们首先看看sThreadLocal.set()
干了什么:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
我们的Looper
对象,是保存在ThreadLocalMap
中,而Thread
类中有一个threadLocals变量来放置ThreadLocalMap
对象,所以,我们的Looper对象,以当前线程对象t
为key
,以当前线程的looper
对象为value
。
我们在调用Looper.myLooper()时,会去取当前线程的Looper对象:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
那又是如何获取的呢:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
根据当前线程,去获取当前线程中的ThreadLocalMap
对象,那么问题来了,作为保存数据的ThreadLocalMap
类,它的数据结构又是什么样的呢?
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
从这里看到,threadLocalMap
是仿HashMap
以键值对的方式存储数据的,key
被保存在弱引用中,value
被保存在Object对象中。这里我查资料查了蛮久,不清楚为什么要将ThreadLocal
对象以弱引用的方式保存,而不是直接用强引用?有了解的朋友麻烦多多留言,谢谢了!
参考文章:
SystemClock.uptimeMillis()理解
Android 中的Looper如何实现阻塞与唤醒的
java new 接口?匿名内部类给你答案
Android Handler机制10之Native的实现
Android中的Looper与epoll
Linux下的进程通信方式: 管道通信详解
深入解析 ThreadLocal 和 ThreadLocalMap
Java四种引用包括强引用,软引用,弱引用,虚引用