背景
安卓里的消息机制,就是Handler-Message-Looper这些,也是安卓开发者应该熟悉的部分。
Looper
一切的开始都来源于Looper.prepare(),如果我们决定在某一个线程新建Handler来处理消息,那在发消息之前,一定要调用Looper.prepare(),这个方法的源代码如下
public static void prepare() {
prepare(true);
}
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,这个方法只能被调用一次;不是空的话,再调用sThreadLocal.set()方法,新建一个Looper对象,传入quitAllowed=true,整体set进sThreadLocal中
Looper对象的构造方法如下,构造了一个属于当前Looper对象的消息队列,并且保存的当前线程
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
那个sThreadLocal在哪里定义?就在Looper类刚开始,代码如下
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
上面一行说道:“sThreadLocal.get()会返回null,除非调用了prepare()”,我们点进去ThreaLocal类的set和get方法
先看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);
}
调用了getMap()方法,如果返回null,就调用createMap()方法,否则把当前ThreadLocal和入参value设置到ThreadLocalMap对象中,此处的当前ThreadLocal就是Looper里的sThreadLocal静态final对象,value就是looper
我们看看getMap()方法对当前线程做了什么
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
返回的是当前线程的threadLocals对象,这个对象是null的话,返回后会调用createMap()方法,这个方法代码如下
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
就是把t.threadLocals实例化了一下,传入的参数this是当前的ThreadLocal对象,也就是Looper里的sThreadLocal对象,firstValue则是looper对象,这俩同时被绑定到了当前线程里的ThreadLocals对象中,至此,线程、sThreadLocals和looper三者被绑到了一起
我们再看看ThreadLocalMap的构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 根据sThreadLocal的哈希值(相当于当前线程的id),获取i,这个i,每个线程里应该是同一个数
table[i] = new Entry(firstKey, firstValue); // 初始化的时候,把sThreadLocal和looper放到固定的首位
size = 1;
setThreshold(INITIAL_CAPACITY); // 把阈值长度缩小为2/3,如果当前size大于INITIAL_CAPACITY * 2 / 3,就扩容,重新计算哈希值
}
其中table是Entry类型的数组,存放键值对(sThreadLocal和looper),看一眼它的构造方法
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k); // k是键,整个entry是一个ThreadLocal(也就是k)的弱引用
value = v;
}
}
最终调用的Reference类的构造方法方法
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = queue;
}
也就是说,entry是sThreadLocal的弱引用,而ThreadLocalMap保存了对Looper.sThreadLocal的弱引用,所以每个线程都保持了对Looper.sThreadLocal的弱引用。
回到ThreadLocal的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);
}
如果getMap()返回的t.threadLocals不是null,调用的是ThreadLocalMap.set()方法,代码如下
private void set(ThreadLocal<?> key, Object value) { // 入参:Looper.sThreadLocal和looper对象
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 当前线程ThreadLocalMap的第一个位置
for (Entry e = tab[i]; // 遍历Entry数组
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); // tab[i]对ThreadLocal对象的引用
if (k == key) { // 找到Looper.sThreadLocal的引用
e.value = value; // 设置looper,返回
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value); // 如果没找到sThreadLocal的引用,考虑新建一个Entry,填补数组中的null
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash(); // 必要时数组扩容,重新计算哈希索引
}
其实正常来说,由于Looper.sThreadLocal对应一个线程,而一个线程只有一个Looper,所以线程里的threadLocals中的Entry数组,里面最多就一项(第一项)不是null,这一项的键值对分别是Looper.sThreadLocal和此线程的Looper对象。故而上面代码段里的遍历循环,应该也会只执行一次
ThreadLocal.set()方法就是这样,接下来看ThreadLocal.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; // 返回entry的value
}
}
return setInitialValue(); // map或e是null,就创建一个新的map,把this和一个null分别作为键值对插入进去,键就是entry的引用
// 同时返回null
}
先是获取当前线程的ThreadLocalMap,再调用ThreadLocalMap.getEntry()方法,传入Looper.sThreadLocal静态对象,而后如果获取的Entry不是null,返回Entry的value,如果ThreadLocalMap和Entry有一个是null,就调用ThreadLocal.setInitialValue()方法。
重点就看ThreadLocalMap.getEntry()和ThreadLocal.setInitialValue()方法
先看前者,ThreadLocalMap.getEntry()源码如下
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1); // 根据key的哈希值获取目标entry的下标索引
Entry e = table[i]; // 实际i是当前线程里ThreadLocalMap的第一个位置,因为key(Looper.sThreadLocal)是唯一的
if (e != null && e.get() == key)
return e; // 获取entry
else
return getEntryAfterMiss(key, i, e); // 或null
}
e.get()方法代码如下
public T get() {
return getReferent();
}
getReferent()方法是Reference类的native方法,应该是获取弱引用本身的对象,换句话说,结果应该是Reference的referent属性,其值则是在其构造方法中赋的,实际应该是Looper.sThreadLocal对象,详情请参加上文对Thread.threadLocals对象构造方法的解读。如果出了意外,e.get()获取到的对象不是Looper.sThreadLocal,就调用ThreadLocal.getEntryAfterMiss(),不过这种情况下,这个方法一般返回就是null了,它是遍历table,也就是遍历Entry数组,而Entry数组一个线程里一般就第一个可能有效,连第一个都不行了,应该就返回null了
然后回到ThreadLocalMap.getEntry()方法,e.get()按理说就是传进来的key(因为key就是Looper.sThreadLocal对象),所以就直接返回了对象e。之后返回ThreadLocal.get()方法,正常情况下就直接把e的value,也就是这个线程的looper返回出去,就完事儿了
ok,Looper.prepare()方法就是这样,它的作用就是给当前线程绑定一个静态常量sThreadLocal和一个new出来的Looper对象,其中sThreadLocal可以根据当前线程,获取保存在当前线程里ThreadLocalMap.Entry里的looper对象
然后看Looper.prepare()方法,这个方法是用来启动当前线程的消息轮询的,代码如下
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(); // 从这个线程的消息队列的队首取出一个消息
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
..
try {
msg.target.dispatchMessage(msg);
..
} finally {
..
}
..
msg.recycleUnchecked();
}
}
去除无关的日志等代码后,其实就上面这几行。先是调用Looper.myLooper()方法获取looper对象,进去这个方法看看:
public static @Nullable Looper myLooper() {
return sThreadLocal.get(); // 此时ThreadMap的value是一个looper,这里就返回looper
}
可见,就是从sThreadLocal中获取当前线程的looper,详情参见上面对sThreadLocal的分析。如果没有调用Loop.prepare()通过sThreadLocal给当前线程实例化looper的话,这里就会返回null,在loop()方法中抛出异常。
然后回到Looper.loop()方法,进入死循环,先是从此线程的消息队列队首获取一个消息,获取完后,调用msg.target.dispatchMessage()方法,这个msg.target就是一个handler对象,这一步就是让handler处理消息的,最后调用msg.recycleUnchecked()方法,回收这个消息到消息队列的队首
于是我们进入MessageQueue类的阅读
MessageQueue
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; // 如果队列内有消息,mPtr就不会是0
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
..
nativePollOnce(ptr, nextPollTimeoutMillis); // 阻塞的native方法,会一直等到队列中有消息可获取
synchronized (this) { // 同步
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; // msg是要获取的队首Message
if (msg != null && msg.target == null) {
.. // 一般msg.target不会是null
}
if (msg != null) { // 如果队列不为空
if (now < msg.when) {
// 还不到发送时间
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; // 老队首后继为null,就从队列中断开
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse(); // 设置标志位
return msg; // 返回
}
} else { // 队列为空,啥也不做,重新循环,回到nativePollOnce()方法阻塞
// No more messages.
nextPollTimeoutMillis = -1;
}
.. // 其他处理
}
..
}
}
主要是判断队首是否为空,为空的话,阻塞在死循环里;不为空的话,从队首获取消息,返回之
这个MessageQueue.next()方法是从消息队列的队首中获取消息,而相应的往队列里添加消息,调用的则是MessageQueue.enqueueMessage()方法,代码如下
boolean enqueueMessage(Message msg, long when) {
.. // 合法判断
synchronized (this) {
..
msg.markInUse(); // 标志位
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 消息队列是空,或队首的when大于传入msg的when(也就是前者发送时间更晚),就把msg作为队首
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 否则
// 如果采用默认的Handler构造方法,msg就不是异步的,needWake为false
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
..
} // 从队列开头遍历,遍历到队列末尾
msg.next = p; // invariant: p == prev.next
prev.next = msg;
// 最终是把msg放到消息队列队尾
}
..
}
return true;
}
可以看到,消息入队的时候,首先判断队列是否为空,或队首消息的发送时间是否比传入的msg发送时间晚,是的话,就把传入的msg放在队首;否则就放在队尾
故而,在looper进行消息循环的时候,从队首读取消息,从队尾添加消息。
从MessageQueue的这俩主要的方法可以看到,这个类主要维护了一个mMessages为队首的消息队列,这个队列是让Looper来从表头获取消息并进行处理的,而这个mMessages也是一个Message对象,所以,Message类本身就是的链表,MessageQueue则主要是维护一个消息的处理队列。
回到Looper.loop()方法,从消息队列队首获取消息后,如果不是null,就调用msg.target.dispatchMessage(msg)方法,target就是Handler对象,下面我们就看看Handler的这个方法
Handler
Handler.dispatchMessage()方法代码如下
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
.. // 回调
} else {
.. // 回调处理
handleMessage(msg);
}
}
handleMessage()方法是在这儿调用的,这个方法需要我们自己实现
另外,我们在利用Handler发消息的时候,经常会用到handler.obtainMessage()来从消息队列中获取消息对象,这个方法代码如下
public final Message obtainMessage()
{
return Message.obtain(this);
}
调用了Message的静态方法obtain(),传入参数为handler类自身,我们进入Message类
Message
静态方法obtain()方法代码如下
public static Message obtain(Handler h) {
Message m = obtain(); // 从链表中获取表头
m.target = h; // message.target指向handler自己,一般不会是null
return m;
}
私有方法obtain方法代码如下
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) { // 消息回收队首不是null
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m; // 从消息回收队首获取消息,队列长度-1
}
}
return new Message(); // 队首是null,返回一个新的Message
}
sPool是消息回收队的队首,静态对象,所以每个线程只有一个回收消息队列。
sPool作为队首,一开始是null,那何时不是null呢?在Message的recycleUnchecked()方法中会被赋值,而这个方法,也是Looper.loop()死循环的最后一步,表示消息队列的队首消息处理完后,会变成消息回收队列的队首。Message.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++; // 插入到消息回收队列队首
}
}
}
到此,Message中关于消息轮询机制的代码大致如此。可见Message中也维持了一个消息队列,但这个消息队列不是MessageQueue中的待处理的消息队列,而是一个回收在Looper中被处理过的消息的。
回到Looper
关于子线程的消息轮询大体如上所示,那么主线程呢?我们在主线程里new Handler的时候,并没有调用Looper.prepare()和Looper.loop(),依旧可以正常完成消息的处理,原因何在?
其实Looper里有一对专门给主线程准备的prepare()以及getMainLooper()方法,以及主线程的Looper对象,它们的相关代码如下
private static Looper sMainLooper; // guarded by Looper.class
....
public static void prepareMainLooper() {
prepare(false); // 主线程的allowQuit是false,意味不可退出
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
应该不用多解释,那么他们在哪儿被调用呢?在ActivityThread.main()方法里,这也是我们应用程序的入口
public static void main(String[] args) {
....
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
....
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
创建主线程的Looper后就进程活动线程(也就是主线程)和handler的创建,最后Looper.loop()进行循环。
结语
从阅读源码的过程来看,可以得出以下几个结论:
1、每个线程都最多有一个Looper、MessageQueue和消息回收队列sPool
2、消息处理流程大概是这样:Looper调用prepare()为当前线程创建looper对象,调用loop()方法轮询读取消息队列的队首消息,如果没有退出消息循环,而后利用handler进行消息处理,最后回收消息到此线程的消息回收队列队首中;至于Handler,它是往消息队列MessageQueue队尾塞进一个新消息,Looper则是在队首读取消息
3、一定要把自定义handler声明为静态的,因为比如在Activity中,Activity要销毁了,handler却还在处理消息,就导致Activity对象不能被销毁,导致内存泄漏,所以要把自定义handler定义为静态,让它的生命周期和activity等对象的生命周期脱离
4、主线程的自定义handler不用调用Looper.prepare()和Looper.loop()是因为ActivtiyThread在main()方法里已经调用了;而在子线程里如果定义handler要处理消息,就必须手动先后调用Looper.prepare()和Looper.loop()方法