Handler并不是專門用于更新UI稚配,它只是常被開發(fā)者用來更新UI。
Android的消息機制主要指Handler的運行機制柏副,底層需要MessageQueue和Looper的支撐勾邦。MessageQueue是采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲消息列表的,Looper為消息循環(huán)割择。由于MessageQueue是一個消息的存儲單元眷篇,不能處理信息,Looper就填補了這一功能荔泳,Looper會以無限循環(huán)的方式去查找是否有新消息蕉饼,有就處理,沒有就等待玛歌。Looper還有一個ThreadLocal昧港,可以在每個線程中存儲數(shù)據(jù)。通過ThreadLocal可以獲取每個線程的Looper沾鳄,一般線程默認是沒有Looper慨飘,除了UI線程,所以就必須創(chuàng)建Looper译荞。
一瓤的、Android消息機制概述
Handler的主要功能是將一個任務(wù)切換到某個指定的線程中去操作,這是因為Android規(guī)定UI操作只能在主線程吞歼,不然就會拋出異常圈膏,原因是因為ViewRootImpl對UI操作做了驗證,通過ViewRootImpl的checkThread方法來完成篙骡。
系統(tǒng)提供Handler的主要原因是為了解決子線程中無法訪問到UI的矛盾稽坤。
這是因為Android的UI控件不是線程安全的,如果多線程訪問會導(dǎo)致UI處于不可預(yù)期狀態(tài)糯俗,如果加上鎖機制尿褪,會有兩個問題:
- 加上鎖會導(dǎo)致讓UI訪問的邏輯變得復(fù)雜
- 降低UI的訪問效率
一般創(chuàng)建Handler的時候需要有Looper,不然會出現(xiàn)異常得湘。
Handler創(chuàng)建以后杖玲,會同內(nèi)部的Looper以及MessageQueue一起協(xié)同工作,然后Handler的post方法將一個Runanble投遞到Handler內(nèi)部的Looper中去處理淘正,其實post也是通過send方法來完成的摆马,當調(diào)用send方法時臼闻,會調(diào)用MessageQueue的enqueueMessage方法將消息放到消息隊列中,然后Looper發(fā)現(xiàn)有新的消息來時囤采,則會處理這個消息述呐,最后消息中的Runnable或者Handler的handleMessage方法會被調(diào)用。注意Looper是運行在創(chuàng)建Handler所在的線程中的蕉毯,這樣一來Handler中的業(yè)務(wù)邏輯就被切換到創(chuàng)建Handler所在的線程中去執(zhí)行乓搬。
二、Android的消息機制分析
1.ThreadLocal的工作原理
ThreadLocalThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類代虾,通過它可以在指定的線程中存儲數(shù)據(jù)缤谎,數(shù)據(jù)存儲以后,只有在指定線程中可以獲取到存儲的數(shù)據(jù)褐着,對于其它線程來說無法獲取到數(shù)據(jù)坷澡。ThreadLocal另一個使用場景是復(fù)雜邏輯下的對象傳遞,比如監(jiān)聽器的傳遞含蓉,有些時候一個線程中的任務(wù)過于復(fù)雜频敛,這可能表現(xiàn)為函數(shù)調(diào)用棧比較深以及代碼入口的多樣性,在這種情況下馅扣,我們又需要監(jiān)聽器能夠貫穿整個線程的執(zhí)行過程斟赚,這個時候可以怎么做呢?其實就可以采用ThreadLocal差油,采用ThreadLocal可以讓監(jiān)聽器作為線程內(nèi)的全局對象而存在拗军,在線程內(nèi)部只要通過get方法就可以獲取到監(jiān)聽器。
介紹了那么多ThreadLocal的知識蓄喇,可能還是有點抽象发侵,下面通過實際的例子為大家演示ThreadLocal的真正含義。首先定義一個ThreadLocal對象妆偏,這里選擇Boolean類型的刃鳄,如下所示:
private ThreadLocal<Boolean>mBooleanThreadLocal = new ThreadLocal<Boolean>();
然后分別在主線程、子線程1和子線程2中設(shè)置和訪問它的值钱骂,代碼如下所示:
mBooleanThreadLocal.set(true);
Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
mBooleanThreadLocal.set(false);
Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
};
}.start();
new Thread("Thread#2") {
@Override
public void run() {
Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
};
}.start();
在上面的代碼中叔锐,在主線程中設(shè)置mBooleanThreadLocal的值為true,在子線程1中設(shè)置mBooleanThreadLocal的值為false见秽,在子線程2中不設(shè)置mBooleanThreadLocal的值愉烙,然后分別在3個線程中通過get方法去mBooleanThreadLocal的值,根據(jù)前面對ThreadLocal的描述解取,這個時候步责,主線程中應(yīng)該是true,子線程1中應(yīng)該是false,而子線程2中由于沒有設(shè)置值勺择,所以應(yīng)該是null,安裝并運行程序伦忠,日志如下所示:
D/TestActivity(8676):[Thread#main]mBooleanThreadLocal=true
D/TestActivity(8676):[Thread#1]mBooleanThreadLocal=false
D/TestActivity(8676):[Thread#2]mBooleanThreadLocal=null
從上面日志可以看出省核,雖然在不同線程中訪問的是同一個ThreadLocal對象,但是它們通過ThreadLocal來獲取到的值卻是不一樣的昆码,這就是ThreadLocal的奇妙之處气忠。結(jié)合這這個例子然后再看一遍前面對ThreadLocal的兩個使用場景的理論分析,大家應(yīng)該就能比較好地理解ThreadLocal的使用方法了赋咽。ThreadLocal之所以有這么奇妙的效果旧噪,是因為不同線程訪問同一個ThreadLocal的get方法,ThreadLocal內(nèi)部會從各自的線程中取出一個數(shù)組脓匿,然后再從數(shù)組中根據(jù)當前ThreadLocal的索引去查找出對應(yīng)的value值淘钟,很顯然,不同線程中的數(shù)組是不同的陪毡,這就是為什么通過ThreadLocal可以在不同的線程中維護一套數(shù)據(jù)的副本并且彼此互不干擾米母。
ThreadLocal是一個泛型類,它的定義為public class ThreadLocal<T>毡琉,只要弄清楚ThreadLocal的get和set方法就可以明白它的工作原理铁瞒。
首先看ThreadLocal的set方法,如下所示:
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
在上面的set方法中桅滋,首先會通過values方法來獲取當前線程中的ThreadLocal數(shù)據(jù)慧耍,如果獲取呢?其實獲取的方式也是很簡單的丐谋,在Thread類的內(nèi)容有一個成員專門用于存儲線程的ThreadLocal的數(shù)據(jù)芍碧,如下所示:ThreadLocal.Values localValues,因此獲取當前線程的ThreadLocal數(shù)據(jù)就變得異常簡單了号俐。如果localValues的值為null师枣,那么就需要對其進行初始化,初始化后再將ThreadLocal的值進行存儲萧落。下面看下ThreadLocal的值到底是怎么localValues中進行存儲的践美。在localValues內(nèi)部有一個數(shù)組:private Object[] table,ThreadLocal的值就是存在在這個table數(shù)組中找岖,下面看下localValues是如何使用put方法將ThreadLocal的值存儲到table數(shù)組中的陨倡,如下所示:
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
面的代碼實現(xiàn)數(shù)據(jù)的存儲過程,這里不去分析它的具體算法许布,但是我們可以得出一個存儲規(guī)則兴革,那就是ThreadLocal的值在table數(shù)組中的存儲位置總是為ThreadLocal的reference字段所標識的對象的下一個位置,比如ThreadLocal的reference對象在table數(shù)組的索引為index,那么ThreadLocal的值在table數(shù)組中的索引就是index+1杂曲。最終ThreadLocal的值將會被存儲在table數(shù)組中:table[index + 1] = value庶艾。
上面分析了ThreadLocal的set方法,這里分析下它的get方法擎勘,如下所示:
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
可以發(fā)現(xiàn)咱揍,ThreadLocal的get方法的邏輯也比較清晰,它同樣是取出當前線程的localValues對象棚饵,如果這個對象為null那么就返回初始值煤裙,初始值由ThreadLocal的initialValue方法來描述,默認情況下為null噪漾,當然也可以重寫這個方法硼砰,它的默認實現(xiàn)如下所示:
/**
* Provides the initial value of this variable for the current thread.
* The default implementation returns {@code null}.
*
* @return the initial value of the variable.
*/
protected T initialValue() {
return null;
}
從ThreadLocal的set和get方法可以看出,它們所操作的對象都是當前線程的localValues對象的table數(shù)組欣硼,因此在不同線程中訪問同一個ThreadLocal的set和get方法题翰,它們對ThreadLocal所做的讀寫操作僅限于各自線程的內(nèi)部,這就是為什么ThreadLocal可以在多個線程中互不干擾地存儲和修改數(shù)據(jù)诈胜,理解ThreadLocal的實現(xiàn)方式有助于理解Looper的工作原理遍愿。
2.消息隊列的工作原理
MessageQueue主要包含兩個操作:插入和讀取,插入和讀取分為enqueueMessage和next耘斩,其中enqueueMessage是往消息隊列中插入一條消息沼填,next是從消息隊列中去出一條消息并從消息隊列中移除,MessageQueue實際上是使用單鏈表來實現(xiàn)的括授。
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;
}
它主要是實現(xiàn)單鏈表的插入操作坞笙。
Message next() {
....
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;
}
}
}
}
next是一個無限循環(huán)的方法,如果沒有消息荚虚,則一直阻塞薛夜,如果有消息則繼續(xù)運行。
3.Looper的工作原理
Looper在Android的消息機制中就是用來進行消息循環(huán)的版述。它會不停地循環(huán)梯澜,去MessageQueue中查看是否有新消息意狠,如果有消息就立刻處理該消息剂公,否則就一直等待趟庄。
首線來看下Looper的構(gòu)造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
通過Looper.prepare()即可為當前線程創(chuàng)建一個Looper帅掘,接著通過Looper.loop()來開啟消息循環(huán):
new Thread("Thread 2"){
public void run(){
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}
Looper中除了prepare方法外,還提供了prepareMainLooper方法挺物,這個方法主要是給主線程創(chuàng)建Looper使用的忱屑,即是屬于ActivityThread的Looper篮昧,其本質(zhì)也是通過prepare來實現(xiàn)的母债,同時Looper也提供一個getMainLooper方法午磁,通過它可以在任何地方獲取ActivityThread的Looper尝抖。Looper也是可以退出的,它提供了quit和quitSafely方法來退出迅皇,二者的區(qū)別是:quit直接退出Looper昧辽,quitSafely只是設(shè)置一個退出標志,等消息隊列中的消息處理完畢登颓,就會退出Looper搅荞,這時候Handler的send方法返回false。如果在子線程中手動創(chuàng)建了Looper挺据,要記得退出Looper,不然這個線程會一直處于等待轉(zhuǎn)態(tài)脖隶,如果退出了扁耐,這個線程也會被終止。
Looper最重要的方法是loop(),只有調(diào)用才能使消息循環(huán)起來产阱,源碼實現(xiàn)如下:
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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
Looper的loop方法也很好理解婉称,loop是一個死循環(huán)的方法,唯一跳出死循環(huán)的方式是MessageQueue的next方法返回null构蹬。當Looper的quit方法被執(zhí)行的時候王暗,Looper會調(diào)用MessageQueue的quit或者quitSafely方法來通知消息隊列退出,當被標記退出庄敛,消息隊列就會返回null俗壹。loop的阻塞是因為MessageQueue的next方法阻塞造成的。如果MessageQueue的next方法返回新消息了藻烤,那就會調(diào)用msg.target.dispatchMessage(msg)來處理绷雏,其中msg.target是Handler對象,dispatchMessage雖然是通過Handler調(diào)用的怖亭,但是是在創(chuàng)建Handler時所使用的Looper中執(zhí)行的涎显,也就是在當前Looper中或者線程中執(zhí)行的,這樣就可以實現(xiàn)線程的切換兴猩。
4. Hanler的工作原理
Handler的工作主要包含消息的發(fā)送和接受過程期吓。消息的發(fā)送可以通過post或者send的一系列方法來實現(xiàn),如下所示:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
/**
* Sends a Message containing only the what value.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
/**
* Sends a Message containing only the what value, to be delivered
* after the specified amount of time elapses.
* @see #sendMessageDelayed(android.os.Message, long)
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
/**
* Sends a Message containing only the what value, to be delivered
* at a specific time.
* @see #sendMessageAtTime(android.os.Message, long)
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageAtTime(msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Handler的發(fā)送消息就是向MessageQueue隊列插入一條消息倾芝,然后MessageQueue就會調(diào)用next方法返回這條消息給Looper讨勤,Looper會開始交由Handler處理,即Handler的dispatchMessage方法會被調(diào)用晨另,dispatchMessage的實現(xiàn)如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Handler的消息處理過程如下:
- 首先先檢查Message的callback是否為空悬襟,不為空就通過handleCallback來處理,Message的callback是一個Runnable對象拯刁,實際上就是Handler的post方法所傳遞的Runnable參數(shù)脊岳,handlerCallback的邏輯如下:
private static void handleCallback(Message message) {
message.callback.run();
}
- 其次檢查mCallback是否為null,不為null就調(diào)用mCallback的handleMessage方法來處理,Callback是個接口割捅,定義如下:
public interface Callback {
public boolean handleMessage(Message msg);
}
通過Callback可以采用如下方法創(chuàng)建Handler handler = new Handler(callable)奶躯。在日常開發(fā)中,創(chuàng)建Handler最常見的方式是派生一個Handler的子類并且重寫它的handleMessage方法亿驾,另外一種是傳入一個Callback嘹黔,并實現(xiàn)callback的handleMessage方法。
最后調(diào)用Handler的handleMessage方法來處理消息莫瞬。
處理過程的流程圖:
Handler還有一個特殊的構(gòu)造方法儡蔓,通過一個特定的Looper來構(gòu)造Handler,實現(xiàn)一些特殊的功能疼邀。
三喂江、主線程的消息循環(huán)
Android的主線程是ActivityThread,主線程的方法入口是mian方法旁振,main方法通過Looper.prepareMainLooper()來創(chuàng)建主線程的Looper和MessageQueue获询,并通過Looper.loop()來啟動循環(huán)。主線程的消息循環(huán)開始后拐袜,需要一個Handler來進行處理和發(fā)送吉嚣,這個就是ActivityThread.H,內(nèi)部有一組消息類型。ActivityThread通過ApplicationThread和AMS進行通信蹬铺,AMS一進程間通信完成ActivityThread的請求后回調(diào)ApplicationThread的Binder方法尝哆,然后像H發(fā)送消息,H收到后會將ApplicationThread中的邏輯切換到ActivityThread中去執(zhí)行甜攀,即切換到主線程中较解。
CSDN博客:http://blog.csdn.net/aaaaa_sean_m/article/details/74079912