基于Android9.0,了解Android消息机制
还是那句话:点成线,线成面,切勿贪心,否则一脸懵逼
由于Android的主线程(UI线程)是非安全的,而且Android开发规范的限制,不能在UI子线程中访问UI控件,否则就会出发程序异常。这个时候,就可以通过Handler来将更新UI的操作切换到UI线程中执行。因此,Handler被大家经常用来更新UI,但是从本质上说,Handler并不是专门用于更新UI的。
Android的消息机制主要是指Handler的运行机制,然后得到MessageQueue、Looper的支撑。
接下来,我们来了解这三个类
- Handler
- ThreadLocal
- MessageQueue
- Looper
还是从源码出发。
Handler有两种发送消息的方式
第一种
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {//重写Handler的handlerMessage方法,因为Handler的handlerMessage是一个空实现
super.handleMessage(msg);
}
};
Message msg = Message.obtain();//或者new Message();
msg.what = 1;
msg.obj = "fat";
handler.sendMessage(msg);
Handler的handleMessage方法
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {//空实现
}
先看Handler源码
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();//注意这里,获取当前线程的Looper。至于什么时候创建的,这条路走通后,会来说明
if (mLooper == null) {//如果没有获取到Looper,会异常。这里证明:线程没有Looper对象,无法创建Handler
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//把线程的MessageQueue与Handler绑定。MessageQueue原理,下面说介绍
mCallback = callback;
mAsynchronous = async;
}
看源码发现,该构造方法,就是获取当前线程的Looper,然后与Handler绑定。
下面看Message msg = Message.obtain();//或者new Message();
public static Message obtain() {//从Message池里获取
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;//Message池是链表结构,获取效率高与数组。
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();//池里没有,还是会new Message()
}
handler.sendMessage(msg);
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
...
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;//得到构造方法时的消息队列
if (queue == null) {//为空抛异常
...
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
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) {
// 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;
//这里链式插入。msg插入到p的后面
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
这里,就完成了Handler的sendMessage,最后enqueueMessage到MessageQueue中。
第二种
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
}
});
或
Handler handler = new Handler();//Lambda表达式
handler.post(()->{
});
创建Handler和第一种方式一样,直接看post
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
sendMessageDelayed后的流程,和第一方式一样,我们来看不同点,看getPostMessage
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;//这里把我们创建的Runnable,赋值给callback
return m;// 返回带有callback的Message
}
到这里,Handler基本结束。
接下来,我们先来看看ThreadLocal,为什么需要先看ThreadLocal呢?
因为涉及到如何保证一个线程对应一个Looper,并且各个线程之间的Looper互不干扰,所以我们先来了解ThreadLocal。
ThreadLocal
ThreadLocal是一个泛型类
public class ThreadLocal<T> {
然后,看看类结构(基于9.0),发现,有get、set、remove等方法,应该能感觉到,这个类大方向的作用。
我们先来看看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();
}
我们先看看getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
继续看threadLocals
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
到这里,发现getMap返回的就是当前线程的ThreadLocal.ThreadLocalMap。然后,我们去看ThreadLocalMap的getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);//注意这里
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);//注意这里
}
threadLocalHashCode是一个哈希值,防止相同的线程获取同一个对象。
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and
* inheritableThreadLocals). The ThreadLocal objects act as keys,
* searched via threadLocalHashCode. This is a custom hash code
* (useful only within ThreadLocalMaps) that eliminates collisions
* in the common case where consecutively constructed ThreadLocals
* are used by the same threads, while remaining well-behaved in
* less common cases.
*/
private final int threadLocalHashCode = nextHashCode();
这里的table是一个Entry数组
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
Entry是ThreadLocalMap一个内部类
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
到这里,我们可以得出结论,ThreadLocal的get方法,获取的是:当前线程下的ThreadLocalMap里面的table中的item,至于这个item里面具体是什么,我们去看看set()方法,继续
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//这里上面分析过了,就是获取当前线程下的threadLocals
if (map != null)
map.set(this, value);//注意这里
else
createMap(t, value);//注意这里
}
继续createMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
这里我们发现,当前线程下threadLocals为null的话,会构造一个ThreadLocalMap,且初始化table。我们再看看map的set
private void set(ThreadLocal<?> key, Object value) {
...
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这里,我们得出结论:ThreadLocal主要是维护当前线程下的ThreadLocal.ThreadLocalMap,每次get或set时,都会先通过当前线程的哈希&内部table数组长度拿到table的index值,而当前线程就为这个table item的key。
至于table如何确定位置、如何防止重复、如何扩容,有时间再慢慢学习,还是那句话:先点成线,再线成面,切勿贪心,否则一脸懵逼。
好了,现在来看Looper
Looper
在Handler构造中,有这么一句话
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;//这里关联handler和looper的MessageQueue
看myLooper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
//Looper类中申明sThreadLocal
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
这里发现一个问题,在整个过程中,没发现sThreadLocal调用set代码,why?
这里2个点,注意:
1.看胖子之前的文章Android启动流程,可知。在调用ActivityThread的main方法时,会调用Looper.prepareMainLooper(),这里就创建了主线程的Looper。
2.子线程,需要手动调用Looper的prepare方法来创建Looper,否则会抛出异常,看Handler源码可知。
这里,我们以主线程为主,来看看ActivityThread的main方法
public static void main(String[] args) {
...
Looper.prepareMainLooper();//注意这里
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
...
Looper.loop();//注意这里,开启消息循环
throw new RuntimeException("Main thread loop unexpectedly exited");
}
来看看Looper.prepareMainLooper
public static void prepareMainLooper() {
prepare(false);//注意这里
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();//上面已经介绍过了,会调用sThreadLocal.get()
}
}
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));
}
到这里,真相大白了。现在剩下最后二个问题:MessageQueue以及三者如何贯穿起来的,分析源码虽然很枯燥,不过貌似看完受益匪浅,内功会提升很多。
MessageQueue
在new Looper时,会创建MessageQueue,我们来看看Looper
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);//创建MessageQueue
mThread = Thread.currentThread();//关联当前线程
}
看看构造方法MessageQueue
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;//用于标示消息队列是否可以被关闭,主线程的消息队列不可关闭
mPtr = nativeInit();//native方法
}
这里很简单,就两行代码,这里还是先看这条线,至于MessageQueue的内部实现,有时间,再来和大家一起研究、学习,这里提一句,MessageQueue内部是以链表的形式。
接下来,来看看几个类,如何贯穿、运作起来的。
全面贯穿
记得ActivityThread,main方法吗?里面调用了Looper.loop(),来看看。
public static void loop() {
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 注意这里,可能会阻塞
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);//注意这里,msg.target为对应的Handler
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
...
}
...
msg.recycleUnchecked();
}
}
先看queue.next()
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
// nextPollTimeoutMillis该参数用于确定消息队列中是否还有消息,从而决定消息队列应处于出队消息状态 or 等待状态。
//nextPollTimeoutMillis为-1,消息队列处于等待状态
for (;;) {//死循环获取message
...
//阻塞操作,当等待超时或者消息队列被唤醒,才会继续
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
Message prevMsg = null;
Message msg = mMessages;
...
if (msg != null) {
if (now < msg.when) {
...
} 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;
}
...
}
}
记住:点成线,线成面,切勿贪心,否则一脸懵逼。先不要去管为什么死循环不会造成线程阻塞,后续再走这条线。
接着再来看dispatchMessage
//如果你记得handler的两种方式(忘记了,看上面),就知道callback代表什么了
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//使用post方式
handleCallback(msg);
} else {//sengMessage方式
if (mCallback != null) {//我们在构成handler时,mCallback传入的null
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);//注意这里
}
}
先看msg.callback == null的时候,调用的handleMessage
/**
* Subclasses must implement this to receive messages.子类必须实现它来接收消息。
*/
public void handleMessage(Message msg) {
}
这里是一个空实现,子类需要覆盖,这里其实就是我们构造Handler时的
再来看看msg.callback != null的时候,handleCallback方法
private static void handleCallback(Message message) {
message.callback.run();
}
message.callback为我们传入的Runnable,所以这里最后调用传入的Runnable的run方法。
至此,handler机制差不多完结了。
关于总结:还是不借鉴各路大神的blog总结了。胖子觉得这样会造成部分朋友只看总结,不看内容,最后变成知其然不知其所以然(胖子就吃过很多这样的亏),还是交给朋友们自行总结吧。
胖子总结
- 先搞清楚Handler、Looper、ThreadLocal、MessageQueue的基本实现
- 自己用工具画一画它们之间的关系
- 温馨提示:点成线,线成面,切勿贪心,否则一脸懵逼
- 胖子有什么理解错误的,欢迎大家指出来,一起讨论、学习、进步
- 期待胖子的第三篇《基于Android9.0,了解Android事件分发(一)》