Android 消息處理機制
Android中常說的消息處理機制, 其實就是android下線程間的通信機制, 也就是Handler的工作機制.
Handler, 我想對于每一個有android開發(fā)經驗的人來說都不會陌生, 經常使用到.
比如, 我們要在子線程中更新界面UI, 會通過handler.sendMessage()發(fā)送一個消息到主線程; 然后在handler的回調方法handleMessage()中去處理這個消息.
我們都知道, android中更新UI只能在主線程進行, 那么, 不知道你有沒有想過, 為什么在子線程中可以通過handler發(fā)送消息給主線程, 來通知主線程更新UI呢?他們是怎么通信的呢?是不是只有主線程才能用Handler的消息機制呢?
一, android消息機制主要由Handler, Message, MessageQueue, Looper幾大角色組成:
Handler: 負責發(fā)送和處理消息
Message: 消息的載體, 封裝了what, arg, target等相關的信息.
MessageQueue: 消息隊列, 存放由Handler發(fā)送過來待處理的Message.
Lopper: 初始化Looper的同時會創(chuàng)建一個MessageQueue, Looper啟動后會一直不斷的從MessageQueue中取出消息Message交給對應的Handler來處理
二, 我們以mainThread為例, 來分析下Handler機制的工作流程
1. Looper初始化
系統(tǒng)啟動時, 在ActivityThread.java的main()中就已經初始化好了, 然后通過Looper.loop()來開啟looper
public static void main(String[] args) {
//...
//創(chuàng)建Looper
Looper.prepareMainLooper();
// ...
//啟動Looper
Looper.loop();
}
接下來先看下Looper.java中創(chuàng)建looper的代碼
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//如果looper已存在, 拋異常,,保證looper的唯一
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
由以上代碼可以看出, 在prepare(boolean quitAllowed) 中我們new了一個Looper對象, 然后保存在sThreadLocal中, 然后通過myLooper()來創(chuàng)建sMainLooper, 即我們的主線程Looper.
代碼邏輯很簡單, 主要是這個sThreadLocal是什么東東?
sThreadLocal 是 ThreadLocal的一個實例. ThreadLocal又是神馬, 不是很懂, 只知道是用來提供thread-local的, 提供了get, set方法. set會保存looper和當前線程的一些信息, get返回looper, 看這個類的注釋了解下
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*
* <p>For example, the class below generates unique identifiers local to each
* thread.
* A thread's id is assigned the first time it invokes {@code ThreadId.get()}
* and remains unchanged on subsequent calls.
* <pre>
* import java.util.concurrent.atomic.AtomicInteger;
*
* public class ThreadId {
* // Atomic integer containing the next thread ID to be assigned
* private static final AtomicInteger nextId = new AtomicInteger(0);
*
* // Thread local variable containing each thread's ID
* private static final ThreadLocal<Integer> threadId =
* new ThreadLocal<Integer>() {
* @Override protected Integer initialValue() {
* return nextId.getAndIncrement();
* }
* };
*
* // Returns the current thread's unique ID, assigning it if necessary
* public static int get() {
* return threadId.get();
* }
* }
* </pre>
* <p>Each thread holds an implicit reference to its copy of a thread-local
* variable as long as the thread is alive and the {@code ThreadLocal}
* instance is accessible; after a thread goes away, all of its copies of
* thread-local instances are subject to garbage collection (unless other
* references to these copies exist).
*
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
public class ThreadLocal<T> {
...
public void set(T value) {
Thread t = Thread.currentThread();
//ThreadLocalMap is a customized hash map
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
//ThreadLocalMap is a customized hash map
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();
}
...
}
順便, 我們看下new Looper()做了什么
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
灰常簡單, 就2行代碼, 一個是初始化我們的MessageQueue, 然后就是獲取到當前的線程mThread .
注意下這里的private構造方法, 即looper是單實例的, 也就是說, 只能通過Looper.prepare()來創(chuàng)建looper對象, 回過去看下prepare()的代碼, 在創(chuàng)建looper前會先判斷下looper是否存在, 如果是已經存在了會直接拋異常的, 這樣就保證了一個線程只能有一個looper, 同時, 也就只能有一個MessageQueue;
2. 啟動looper
嗯嗯,...sMainLooper創(chuàng)建好后, 回到ActivityThread.main(), 通過Looper.loop()啟動looper.
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
//拿到當前線程的looper
final Looper me = myLooper();
//looper為空就拋異常, 這就是為什么普通線程不能用Handler的原因了
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//拿到looper的MessageQueue
final MessageQueue queue = me.mQueue;
//...other code
//無限for循環(huán)
for (;;) {
//拿到MessageQueue中的Message對象, 如果有, 主要這里的might block, 即這個方法會阻塞線程
Message msg = queue.next(); // might block
//...
try {
//dispatchMessage分發(fā)處理Message
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//...other code
//Message回收
msg.recycleUnchecked();
}
}
看loop()的核心代碼, 邏輯也很簡單, 就是拿到當前線程的Looper, 拿到Looper的MessageQueue, 然后一個無限for循環(huán), 不斷的從消息隊列里取出消息, 然后msg.target.dispatchMessage(msg), 來處理消息.
我們重點分析下MessageQueue是怎么拿到消息的:
Message msg = queue.next(); // might block
就是MessageQueue.next()了
Message next() {
// ...other code
int nextPollTimeoutMillis = 0;//等待執(zhí)行的時間
for (;;) {
//消息需要延時執(zhí)行, e.g: postDelayed, sendMessageDelayed這些操作就會有延時
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) {
//找到一個不是異步而且不為空的message.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//時間還沒到, 計算出延時時間, 這里之后會進入下一個循環(huán), 然后在nativePollOnce()中block住
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;
msg.markInUse();
return msg;
}
} else {
// No more messages. 沒有消息了
nextPollTimeoutMillis = -1;
}
// ...
}
// ...
}
}
looper的啟動流程大概就到這里了, 這里有幾個問題有注意下:
?1. 調用myLooper()后, 會先判斷下looper是否為空, 如果空會直接拋異常, 這就是為什么普通線程不能用Handler的原因了, 所有線程如果要有消息處理能力, 必須先Looper.prepare()來創(chuàng)建looper對象;
?2. 分發(fā)消息msg.target.dispatchMessage(msg), 這里的target其實就是關聯(lián)的handler對象, 是在sendMessage()的時候關聯(lián)的, 這樣能夠使Looper在分發(fā)消息的時候是不用去區(qū)分Handler對象的;
?3. queue.next()這里是會阻塞線程的, 而且這里如果是主線程的話, 為什么不會ANR呢? 這個問題要先理清楚產生anr的原因, 并不是有阻塞就會anr, anr是因為主線程的消息來不及處理而導致的. 比如, 我們在啟動一個activity的時候再主線程sleep個10s, 這樣不一定會anr的, 只有當主線程阻塞的時候, 有新的消息要處理又得不到處理, 比如我們按下手機返回鍵, 這時就會anr了.
3. obtain Message
獲取一個消息對象, 一般使用obtain, 當然new也可以.
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
private static Message sPool;
這里的sPool是一個靜態(tài)Message對象, 不為空的時候, 就直接返回這個message, 否則new一個message, 這樣可以減少對象的創(chuàng)建. 那么這個sPool是那來的呢? 就是在Looper分發(fā)消息后, 可以回過去看一下loop()方法for循環(huán)的最后一行代碼, 就是對message的回收操作.
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) {
//將當前message緩存起來, MAX_POOL_SIZE = 50
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
回收前, 先把當前對象的以下狀態(tài), 參數(shù)等清除, 然后把當前對象保存在sPool. 剛才說了, 這個sPool是個靜態(tài)變量, 所有是會一直存在的. 這里Message其實也維護了一個鏈表這種數(shù)據(jù)結構的, 看下在對sPool賦值前會把之前的sPool賦值給next, 就是讓next指向之前回收的Message對象. 當obtain獲取一個Message時, 獲取到的是表頭的Message, 然后又讓sPool指向表頭的next, 即下一個Message對象, 這樣每次obtain時都能獲取到表頭的一個Message對象.
驚不驚喜, 意不意外? 以前沒看源碼前, 一直以為這邊是用對象池的方式來緩存Message對象的.
4. Handler發(fā)送和消息的分配
Handler要發(fā)送消息, 要先獲取一個Handler對象, 一般通過以下兩種方式
- handler = new Handler()
- handler = Handler(Looper looper)
需要一個Looper對象才能創(chuàng)建Handler, 第一種方法其實默認獲取當前線程的Looper的
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;
}
Looper.myLooper()獲取的就是當前線程的Looper. 看下那個異常, 當looper為空的時候拋異常的, 這就是為什么不能再子線程用handler的原因了.
Looper在啟動后, 會一直在死循環(huán)中, 等待消息進入消息隊列.
那么, 消息是怎么進入消息隊列的呢? 就是我們經常用的handler的一些方法了, 比如
sendMessage, sendMessageDelayed, post, postDelayed...
這些方法最終都會執(zhí)行到sendMessageAtTime()這里
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //關聯(lián)handler
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
然后就到MessageQueue.enqueueMessage()加入消息隊列了
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();
//消息執(zhí)行的時間杖刷,隊列是按照消息執(zhí)行時間排序的
//when = SystemClock.uptimeMillis() + delayMillis
msg.when = when;
Message p = mMessages;
boolean needWake;
//列表里沒有消息 / 消息的執(zhí)行時間== 0 / 要入隊列的消息執(zhí)行時間 < 表頭消息的時間
//把這個消息當做表頭, next指向之前的那個消息(mMessages)
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
//需要喚醒線程, 因為在不用處理消息的時候線程是block的, 前面分析過了
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
//從表頭開始遍歷消息, 根據(jù)執(zhí)行時間找出前面(要先執(zhí)行)的那個消息prev
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;
}
// 喚醒線程
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
代碼注釋都有, 不難理解.
接下來分配消息, 看下Handler.dispatchMessage(msg)這部分代碼:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
這部分代碼邏輯很簡單, 大概解釋下: handler.post()時msg.callback != null, 會調用Runnable.run();
如果是通過Handler(Callback callback)實例化handler的話, mCallback != null; 否則直接調用 handleMessage(msg), 這個是空方法, 就是我們初始化Handler時重寫的那個handleMessage()方法.
三, 利用Handler機制來實現(xiàn)子線程間的通信
根據(jù)主線程的消息處理機制的原理, 我們完全可以在子線程中來實現(xiàn)我們自己的消息處理機制.
大概代碼是這樣的:
class MyThread extends Thread {
@Override
public void run() {
super.run();
//prepare looper
Looper.prepare();
//handler
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
...
//start looper
Looper.loop();
}
}
這樣, 只要在其他子線程能拿到MyThread的Handler或是Looper(創(chuàng)建Handler)就能向MyThread發(fā)送消息了, MyThread的looper會自行處理這些消息.
ps: 這幾天在重新理了下Handler機制這塊, 寫下來是為了加深印象. 第一次寫, 如有不對的地方, 請幫忙指正,感謝!感謝!!