相信大部分開發(fā)android的人使用Handler在子線程上去進行ui的操作這種模式已經(jīng)滾瓜爛熟了篮洁,但是當你不去深入研究它的原理帽芽,和理清它與Looper和Message之間的關系時删掀,遇到問題和bug的時候你就會無從下手,手忙腳亂导街。
技術也是一門學問披泪,只知其一不知其二,你永遠只會停留在基礎搬瑰。送給自己也是送給大家的一句話:你若不想做款票,總會找到借口;你若真想做泽论,總會找到方法艾少!
開始進入正題,什么是異步消息處理機制翼悴?
就應用程序而已缚够,android系統(tǒng)中java的應用程序和其他系統(tǒng)上相同,都是靠消息驅動來工作的,它們的大致工作原理是:
*有一個消息隊列潮瓶,可以往這個消息隊列中不斷投遞消息(進)
*有一個消息循環(huán)陶冷,可以不斷的從這個消息隊列中取出消息钙姊,進行處理毯辅。(出)
事件源把待處理的消息加入到消息隊列中(默認是加至隊尾,但是也會遇到插隊的情況出現(xiàn)煞额,而且不是插中間思恐,直接插入至隊列頭,這種是屬于大哥級別的消息)膊毁,處理線程從消息隊列頭不斷取出消息分發(fā)給對應的target進行處理胀莹。這種實現(xiàn)模式在android上主要就是靠我們的Handler,Looper來實現(xiàn)婚温。
為什么需要設計這樣的一種機制來進行處理描焰,因為我們都知道Android UI線程是不安全的,如果嘗試在非ui線程上去進行ui的更新栅螟,這個時候程序是有可能會崩潰的荆秦。那么android推薦的處理方式是:在你的當前activity也就是ui線程上創(chuàng)建一個Handler,并實現(xiàn)它的callback:handleMessage,
當你需要在子線程上去進行ui更新的時候力图,創(chuàng)建一個Message步绸,通過handler發(fā)送出去,在handleMessage中接收到這個message對象進行ui更新吃媒。
一.Handler
我們平時在開發(fā)的過程中都會選擇直接去new一個Handler,如下代碼:
private Handler mHandler=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
然后在線程中通過Message來組裝需要傳遞到ui線程的數(shù)據(jù)瓤介,讓handler來進行發(fā)送,代碼如下:
new Thread(new Runnable() {
@Override
public void run() {
//發(fā)送消息
Message msg = Message.obtain();
msg.what = 111;
Bundle bundle=new Bundle();
bundle.putString("huan","hello");
msg.setData(bundle);
mHandler.sendMessage(msg);
}
}).start();
這樣就可以在主線程的handler回調callback中接收到這個消息并進行處理:
@Override
public boolean handleMessage(Message msg) {
if(msg.what==111){
//取出數(shù)據(jù)
String str = msg.getData().getString("huan");
//...進行ui更新
}
return false;
}
這樣一套基本的異步更新ui的流程就走完了赘那。但是當寫完這些代碼的時候有沒有想過為什么這樣子做可以實現(xiàn)刑桑?表面上看我們緊緊就是new 了一個handler和一個messsage作為載體就達到了這樣的效果,但是實際底層的實現(xiàn)卻比這復雜的多募舟。下面我們可以一起進入源碼來看看到底里面是如何實現(xiàn)的祠斧。
先來看看handler的構造方法,
我們平時只實現(xiàn)了callback這一個參數(shù),點進去一看發(fā)現(xiàn)其實是:
public Handler(Callback callback) {
this(callback, false);
}
下面的 才是最終的一個實現(xiàn):
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到其實當我們在new一個handler的時候胃珍,它里面進行了一個很重要的操作:
mLooper = Looper.myLooper();
我們最重要的Looper入場了,Looper.myLooper()方法獲取了一個Looper對象梁肿,如果Looper對象為空,則會拋出一個運行時異常觅彰,所以想要一探究盡吩蔑,我們得去看看這個Looper這個類,到底扮演著什么角色填抬。
二.Looper
備注:至于Looper這個類到底有什么用烛芬,這里先不做解釋,我們先跟著源碼一步一步走下就會揭開它的面紗
當進入到Looper這個類的時候,查看myLooper()這個方法時發(fā)現(xiàn)就緊緊一行代碼:
public static Looper myLooper() {
return sThreadLocal.get();
}
通過sThreadLocal這個對象來獲取到looper赘娄,那既然是通過get方法來獲取到這個looper仆潮,那必然有set方法來設置這個looper,但是我們自己只是new了一個handler遣臼,并沒有對looper做任何的處理和操作性置,那必然是android系統(tǒng)自己在某個地方給我們做了某些操作,接下來繼續(xù)深入查看Looper這個類揍堰,看看sThreadLocal這個對象到底是在哪兒進行申明的:
public final class Looper {
private static final String TAG = "Looper";
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
一查找發(fā)現(xiàn)鹏浅,居然在類的開頭進行了這個對象的初始化,并且給出了明確的注釋:如果沒有執(zhí)行Looper的prepare()方法那么通過sThreadLocal.get()返回的就是null屏歹。所以隐砸,現(xiàn)在目的就明確了,跟著指示走下去看看prepare()方法里面到底執(zhí)行了什么操作:
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));
}
很明顯的看到蝙眶,原來是在prepare()方法中對sThreadLocal設置了一個新的looper,并且這里也進行了一個非空判斷季希,如果已經(jīng)執(zhí)行過prepare來獲取過一次looper,那么再次調用的時候就會拋出異常幽纷,這樣的做法也是保證了一個線程中只有一個Looper的實例式塌。
備注下:sThreadLocal是一個ThreadLocal對象,可以在一個線程中存儲變量T
看到這里可能大家可能都會覺得很明了霹崎,清晰了珊搀,我們整理下整個思路流程就是:
new一個handle來作為發(fā)送消息的載體
在handler的構造函數(shù)中通過Looper.myLooper()來獲取到了這個Looper
在myLooper()這個方法其實也是通過sThreadLocal這個對象來進行獲取的Looper
想要通過sThreadLocal這個對象來獲取到looper,必須先執(zhí)行Looper的prepare()方法
那么問題來了:
之前我們不是在new一個handler對象的時候尾菇,發(fā)現(xiàn)其實構造函數(shù)里面就是通過Looper.myLooper()方法來獲取了一個looper境析,但是如果Looper對象為空,則會拋出一個運行時異常派诬;并且我們至始至終都沒用使用過這個looper對象劳淆,更不用說執(zhí)行它的prepare方法了,那么為什么我們這樣任性的使用handler默赂,程序確沒有奔潰沛鸵?
一開始我就發(fā)現(xiàn)了這個細節(jié),而且我們在new handler的時候大部分的時候都是在主線程中去操作的缆八,那么猜想必然是android系統(tǒng)在應用程序啟動的時候曲掰,開啟主線程的時候就為這個線程創(chuàng)建了這了Looper對象,提前為我們執(zhí)行了Looper.prepare()方法奈辰。
這個時候你也不用特意去研究android系統(tǒng)啟動的過程就能很輕松的找到在Looper類的prepare()方法下面就是一個prepareMainLooper()函數(shù)
/**
* 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();
}
}
而且注釋給的很詳細了:在當前線程中初始化了一個Looper栏妖,并且作為我們應用程序的main looper,在我們應用程序創(chuàng)建的時候就會創(chuàng)建奖恰,所以不需要我們去手動的去調用這個方法吊趾。
查看源代碼發(fā)現(xiàn)果然是這樣的:在ActivityThread的main方法中
public static final void main(String[] args) {
......
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false);
......
Looper.loop();
......
thread.detach();
......
}
這里小結下:我們應用程序在啟動的時候會給我們創(chuàng)建一個main looper宛裕,并始終存在我們應用程序,所以不需要我們手動去調用Looper.prepare()方法论泛,所以在主線程中的任意地方你都可以放肆的創(chuàng)建Handler揩尸,但是注意:如果是在子線程中創(chuàng)建Handler,
務必先調用Looper.prepare()才能創(chuàng)建Handler對象屁奏。
備注:在ActivityThread中不僅實現(xiàn)了Looper.prepareMainLooper()方法我們還看到有個Looper.loop()方法岩榆,這個方法有什么用,先不著急了解了袁,我們留個伏筆朗恳。
三.消息循環(huán)
上面講了hanlder創(chuàng)建的整個過程,以及如何得到looper的過程载绿,但是始終還是不知道這個looper到底有什么用,是如何操作的油航?接下來回到我們之前的步驟中接著走下去崭庸,在sThreadLocal中設置了一個new Looper(),可以進入Looper的構造函數(shù)看看到底實現(xiàn)了什么:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
這里new出了一個MessageQueue對象,也就是我們另一個角色扮演者:消息隊列谊囚。所有通過handler發(fā)送的消息都會存儲到這個消息隊列來怕享,因此一個Looper對象對應了一個MessageQueue。而在handler的構造函數(shù)中我們看到了:mQueue = mLooper.mQueue 也就是說Handler中的消息隊列變量最終都會指向Looper的消息隊列镰踏。
那就不難理解為什么我們通過
mHandler.sendMessage(msg);
這個代碼就能將消息發(fā)送到Looper的消息隊列中來函筋,handler也為我們提供了一系列函數(shù)來幫助完成創(chuàng)建消息和插入消息隊列的工作:
//從handler中創(chuàng)建一個消息碼是what的消息
public final Message obtainMessage(int what)
//發(fā)送一個只含有消息碼的消息
public final boolean sendEmptyMessage(int what)
//延時發(fā)送一個只含有消息碼的消息
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
//發(fā)送一個消息,默認添加至隊列尾
public final boolean sendMessage(Message msg)
//發(fā)送一個消息奠伪,添加至隊列頭跌帐,優(yōu)先級高
public final boolean sendMessageAtFrontOfQueue(Message msg)
接下來再進入sendMessage中看看如何操作的就明了了:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
最后調用的是sendMessageAtTime這個方法,也就是拿到之前的MessageQueue然后進行了enqueueMessage操作:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
這里將msg的target指向了自己绊率,也就是handler谨敛,然后又調用了queue.enqueueMessage(msg, uptimeMillis)
進去看看:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", 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;
}
大致分析了下,這里主要就是將消息進行了一個時間排序滤否,根據(jù)我們傳入的uptimeMillis.根據(jù)時間的順序調用msg.next.
消息進入隊列已經(jīng)完成脸狸,那么在什么時候進入處理消息,循環(huán)消息藐俺,就是我們之前埋下的伏筆:在ActivityThread中不僅實現(xiàn)了Looper.prepareMainLooper()方法我們還看到有個Looper.loop()方法
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
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
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();
}
}
我們有時看源碼的時候炊甲,沒必要每一行都去理解它,抓重點就行:這個函數(shù)主要創(chuàng)建了一個死循環(huán)欲芹,不斷的調用Message msg = queue.next();來從消息隊列里面取出消息卿啡,并進行處理:msg.target.dispatchMessage(msg) ,而msg.target剛好是我們之前看handler的時候把自己賦值給了這個target耀石;接下來繼續(xù)看看這個target做了什么事:msg.target.dispatchMessage(msg);
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在dispatchMessage方法中可以看到如果消息本身有callback牵囤,則直接交給msg的callback處理爸黄,否則mCallback不為null就會執(zhí)行mCallback.handleMessage(msg);看到這兒相信大家已經(jīng)豁然開朗了,這從我們剛開始講的實現(xiàn)callback的handleMessage方法相對應揭鳞,也就是說消息是從這里傳遞過去的炕贵。這樣我們從開頭到這兒剛好形成一個邏輯的閉環(huán)。一個消息鏈的發(fā)送和接收處理的完整過程我們再進行總結下:
1.無論是在android 應用程序主線程還是其他線程首先會執(zhí)行Looper.prepare()野崇,創(chuàng)建該線程的Looper對象称开,記住:一個線程對應一個Looper對象乓梨;然后在創(chuàng)建Looper的時候創(chuàng)建了一個MessagQueue消息隊列管理消息的入棧和出棧鳖轰,也是一個線程對應一個MessagQueue;
2.執(zhí)行Looper.loop方法讓該線程創(chuàng)建一個死循環(huán)扶镀,不斷的調用Message msg = queue.next();來從消息隊列里面取出消息蕴侣,并進行處理:msg.target.dispatchMessage(msg)
3.創(chuàng)建一個Handler來進行消息的發(fā)送和接收處理,并在初始化的時候與該線程的Looper中的MessagQueue相關聯(lián)臭觉;
4.通過handler發(fā)送message的時候昆雀,會將msg的target設置為handler自己,并且將該msg按照時間進行排序至消息隊列
那么Handler,Looper,Message之間的關系就是:
handler負責不斷發(fā)送message到MessageQueue蝠筑;Looper將消息隊列中的消息一個一個取出回調給dispatchMessage方法狞膘;最后消息回到handler所在的線程,通過handler的callback方法進行處理什乙。
好了挽封,到這里差不多把整個handler異步消息機制梳理完畢;后面還會出一篇文章講解下Looper和handler的同步關系臣镣。以及在多線程中如何處理這個消息的傳遞辅愿;handlerThread的用法;
謝謝退疫。