微信公衆號:碼小豬
碼小豬博客:www.hchstudio.cnnode
從很早開始就認識到 Handler 了,只不過那時修爲尚淺,瞭解的不夠深入,也沒有應用自如。不過隨着工做時間的增加,對 Handler 又有了更深層次的認識,因而有了這篇博客,但願儘量的總結出多的知識點。緩存
Handler 在 Java 層源碼主要有 4 個類:Looper、MessageQueue、Message、Handler。我概括了他們的幾個主要知識點:安全
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
// sThreadLocal
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) { throw Exception ... }
sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
// sMainLooper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) { throw Exception ...}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
// mQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
複製代碼
經過以上分析,咱們能夠總結出一下特性:微信
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next();
...
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked();
}
}
複製代碼
Looper.loop() 方法雖然看起來不少,其實他主要就作了三件事:數據結構
public int what, arg1, arg2;
public Object obj;
public Messenger replyTo;
int flags;
long when; // 消息發送時間
Bundle data;
Handler target;
Runnable callback;
Message next;
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
複製代碼
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
void recycleUnchecked() {
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++;
}
}
}
複製代碼
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) {
// 做爲表頭,若是隊列是阻塞狀態則須要喚醒
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; // 插入消息
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製代碼
主要做爲插入隊列的方法,有下列幾個特性:異步
for (;;) {
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) {
// 計算距離下一個消息的時間
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 {
// 沒有更多消息的時候,nextPollTimeoutMillis 會置爲 1。
nextPollTimeoutMillis = -1;
}
...
}
// 若是目前沒有消息,已經處在空閒狀態,則執行 idler.queueIdle
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);
}
}
}
...
}
複製代碼
此方法會從消息隊列中讀取下一個消息返回,主要作了如下操做:函數
上面咱們有提到了同步消息隔離,這裏咱們介紹一下。同步隔離,有時候也能夠叫異步消息,說的是一個意思。在源碼中主要用於優先更新 UI。oop
private IdleHandler[] mPendingIdleHandlers;
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// 向消息隊列中加入一個 handler 爲空的消息
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
複製代碼
如上 postSyncBarrier 函數中會向消息隊列中加入一個 handler(即 Message 的 target) 爲空的消息做爲標識。在咱們上面 MessageQueue.next() 的函數中,當 msg.target == null 時,會優先獲取異步消息並返回。
所以想要使用異步消息有兩個條件:post
Handler 還提供了消息隊列空閒狀態通知。ui
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
複製代碼
IdleHandler 的源碼比較簡單,就是一個 ArrayList,而後進行增長刪除操做。注意,這個也是線性安全的。
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
任務執行時就會運行這個函數,主要是一個優先級的問題: