我們都知道募寨,系統(tǒng)是不允許在子線程中訪問UI的族展,但如果在主線程中進(jìn)行耗時操作,又會極大地妨礙用戶體驗(yàn)拔鹰。
所以仪缸,我們可以在子線程中進(jìn)行耗時操作,操作完畢后通知主線程更新UI格郁。
為了方便在線程之間進(jìn)行通信腹殿,官方提供了一種很好的解決方法——Handler独悴。有效地解決了在子線程中無法訪問UI的矛盾。
使用Handler锣尉,通常需要繼承Handler類刻炒,并重寫handleMessage()方法。
舉一個簡單的用法例子:
// 在主線程中
MyHandler mHandler = new MyHandler();
private void getDataFromNet() {
new Thread(new Runnable() {
@Override
public void run() {
//耗時操作自沧,完成之后發(fā)送消息給Handler坟奥,完成UI更新;
mHandler.sendEmptyMessage(0);
// 如果想發(fā)送帶參消息拇厢,則用sendMessage()方法
}
}).start();
}
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 這個方法需要自己寫內(nèi)容
switch (msg.what) {
case 0:
//完成主界面更新,拿到數(shù)據(jù)
imageView.setImageResource(R.mipmap.ic_launcher);
break;
default:
break;
}
}
}
在以上的例子中爱谁,我們在主線程創(chuàng)建了一個MyHandler類型的對象mHandler,在子線程完成耗時操作(如網(wǎng)絡(luò)請求獲取圖片)之后孝偎,用sendEmptyMessage()/sendMessage()方法發(fā)送消息访敌,隨后在主線程中會調(diào)用mhandler的handleMessage()方法來處理消息(如顯示圖片)。
怎么樣衣盾,是不是很簡單寺旺?
接下來我們就來看看它內(nèi)部構(gòu)造。
相關(guān)角色
- Handler :負(fù)責(zé)向消息隊列發(fā)送消息势决,以及處理從消息隊列中分發(fā)出來的消息阻塑。
- Looper:負(fù)責(zé)無限循環(huán)檢查消息隊列中的消息。如果有消息則把它取出交給handler處理果复,同時把該消息從消息隊列中移除陈莽。
- MessageQueue(消息隊列):負(fù)責(zé)存儲消息。以單鏈表的結(jié)構(gòu)對外提供插入和刪除的工作虽抄。
角色關(guān)系
在Handler內(nèi)部有mLooper和mQueue兩個成員變量极颓,其中mQueue是mLooper的成員變量朱盐。
由此我們可以知道群嗤,如果在一個線程中沒有Looper菠隆,那么handler便無法向消息隊列中發(fā)送消息,更不用說從中取出消息并處理了狂秘。
但是從開頭的例子中我們也可以看到骇径,在主線程中,并沒有一個Looper被顯式地創(chuàng)建者春,那么為什么在可以直接創(chuàng)建Handler并被使用呢破衔?
這是因?yàn)椋瑧?yīng)用在啟動的時候先執(zhí)行了ActivityThreadMain钱烟,用Looper.prepareMainLooper()創(chuàng)建了一個在主線程進(jìn)行循環(huán)的looper晰筛,并用Looper.loop()開啟消息隊列嫡丙,從而handler可以正常使用。
public static void main(string[] args){ //在這里面創(chuàng)建了一個MainLooper读第,創(chuàng)建的過程如下:
//初始化
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if(sMainThreadHandler == null){
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
//動起來
Looper.loop();
}
以上這段代碼還可以說明一個問題曙博,并不是只有主線程中才能創(chuàng)建Handler,在子線程中也同樣可以怜瞒。
只要在子線程調(diào)用Looper.prepare()父泳,和Looper.loop(),無限循環(huán)讀取消息隊列的looper吴汪,就可以創(chuàng)建Handler了惠窄。
可是,Looper和Handler是怎么關(guān)聯(lián)到一起的呢漾橙?當(dāng)遇到多個繼承了Handler的類時杆融,Looper取出的消息又是怎樣交給正確的Handler的呢?這就需要看一下他們的調(diào)用關(guān)系了霜运。
調(diào)用關(guān)系
Handler在創(chuàng)建的時候擒贸,我們可以看到,它有兩個構(gòu)造方法觉渴。第一個是傳入一個looper變量介劫,為其內(nèi)部的mLooper賦值。
// Handler類
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
第二個比較常用案淋,不往里面?zhèn)鱨ooper座韵,而是自動獲取該線程的looper為其賦值。
// Handler類
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;
}
值得注意的是獲得該線程的looper的語句:mLooper = Looper.myLooper();
它是怎么獲取的呢踢京?我們跟進(jìn)去看一下:
// Looper類
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
這里出現(xiàn)了一個很奇怪的變量——sThreadLocal誉碴。這個變量是ThreadLocal類型的,我們需要簡單了解一下ThreadLocal類瓣距,才能對整個過程有一個更加清楚的認(rèn)識黔帕。
ThreadLocal類解析
ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類(只能存一個值),通過它可以在指定的線程中存儲數(shù)據(jù)蹈丸,數(shù)據(jù)存儲以后成黄,只有在指定線程中可以獲取到存儲的數(shù)據(jù),對去其他線程來說則無法獲取到該數(shù)據(jù)逻杖。
一般來說奋岁,當(dāng)某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時候,就可以考慮采用ThreadLocal荸百。
經(jīng)過前面的講解闻伶,我們可以想到Looper的作用域是線程,并且不同線程具有不同的Looper够话,這個時候通過ThreadLocal就可以輕松實(shí)現(xiàn)Looper在線程中的存取蓝翰。
可能說到這里大家還是不太明白這個類的神奇之處光绕,我舉一個直觀的例子,大家一看就知道啦畜份。
package com.example.cgj.loopertest;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private ThreadLocal<Boolean> threadLocal; //存布爾類型的值
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
threadLocal = new ThreadLocal<>();
threadLocal.set(true); // 在主線程把存的值設(shè)為true
Log.d("threadLocal的值 , 當(dāng)前線程 :",threadLocal.get().toString()+" "+Thread.currentThread().toString());
TestAsyncTask testAsyncTask = new TestAsyncTask();
testAsyncTask.execute();
}
class TestAsyncTask extends AsyncTask<Void,Integer,Void>{
@Override
protected Void doInBackground(Void... voids) {
threadLocal.set(false); // 在子線程把存的值設(shè)為false
Log.d("threadLocal的值 , 當(dāng)前線程 :",threadLocal.get().toString()+" "+Thread.currentThread().toString());
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
// 回到主線程奇钞,打印子線程運(yùn)行過后的值
Log.d("threadLocal的值 , 當(dāng)前線程 :",threadLocal.get().toString()+" "+Thread.currentThread().toString());
}
}
}
請大家先不要看程序運(yùn)行結(jié)果,先推測一下打印的三個值分別會是什么漂坏。
推測完后我們再往下來驗(yàn)證結(jié)果是否正確:
09-05 22:01:35.704 21005-21005/com.example.cgj.loopertest D/threadLocal的值 , 當(dāng)前線程 :: true Thread[main,5,main]
09-05 22:01:35.704 21005-21039/com.example.cgj.loopertest D/threadLocal的值 , 當(dāng)前線程 :: false Thread[AsyncTask #1,5,main]
09-05 22:01:35.724 21005-21005/com.example.cgj.loopertest D/threadLocal的值 , 當(dāng)前線程 :: true Thread[main,5,main]
是不是發(fā)生了一件奇怪的事情景埃?在子線程中明明把threadLocal中存的值設(shè)為了false,之后在主線程打印的值還是true顶别!怎么會是這樣谷徙,難道是沒有設(shè)置成功?
事實(shí)上驯绎,在一個線程對threadLocal進(jìn)行的操作完慧,完全不會對另一個線程造成任何影響。從threadLocal被創(chuàng)建的線程開始分支的每個子線程剩失,都單獨(dú)擁有threadLocal所存儲的值屈尼,當(dāng)這個值在線程內(nèi)進(jìn)行改變時,都不會影響其他線程的值拴孤。
所以脾歧,使用sThreadLocal.get()獲取該線程的Looper是一個很方便并且正確的選擇。
獲取到該線程的Looper之后演熟,再為Handler的mLooper變量賦值鞭执,就完成了該線程里Handler和Looper的關(guān)聯(lián)。
接下來我們再來看芒粹,Handler是怎樣向消息隊列發(fā)送消息的兄纺。
Handler發(fā)送消息的流程
為方便閱讀,我將部分內(nèi)容進(jìn)行了簡化化漆。
最后來到了mQueue.enqueueMessage()這個方法估脆。說明它就是最終把消息加入到消息隊列的關(guān)鍵方法。我們來看一下它的源碼:
// MessageQueue類
boolean enqueueMessage(Message msg, long when) {
// 判斷是否有接收此消息的Handler(用target值來標(biāo)記)座云,若沒有則拋出異常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 判斷該消息是否已經(jīng)加入到消息隊列疙赠,若已加入則拋出異常
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
// 判斷該消息隊列是否已經(jīng)退出。如果退出了疙教,則無法發(fā)送消息棺聊,會拋出異常,并回收該消息贞谓。
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(); // 把該消息標(biāo)記為已加入消息隊列
msg.when = when; // 設(shè)置該消息的傳輸時間
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 如果該消息隊列目前為空,則將該message作為頭節(jié)點(diǎn)
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();
// 將該消息插入到消息隊列(實(shí)現(xiàn)形式為單鏈表)
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;
}
// 中文注釋為作者注葵诈,英文為源碼注裸弦、
稍微有點(diǎn)長祟同,但一點(diǎn)點(diǎn)看完之后是不是明白了許多呢?
其實(shí)理疙,除了send系列方法晕城,還可以用post系列方法來發(fā)送消息(代碼如下)。但post系列方法最終也是通過send系列方法來實(shí)現(xiàn)的窖贤,所以這里只講述了sendMessage()方法的過程砖顷。
// Handler類
public final boolean post(Runnable r) { returnsendMessageDelayed(getPostMessage(r), 0); }
當(dāng)Handler向消息隊列插入一條消息后,Looper便能夠在消息隊列中讀取到該消息赃梧,并將它進(jìn)行分發(fā)滤蝠。這里涉及到兩點(diǎn)問題:讀取消息、分發(fā)消息授嘀。
Looper的工作機(jī)制
Looper是通過loop()方法來使消息循環(huán)系統(tǒng)起作用的物咳,這個方法非常重要。我們簡單地看一下它的內(nèi)容:
// Looper類
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();
}
}
從以上代碼中可以看到蹄皱,loop()方法里面有一個死循環(huán)览闰,只有queue.next()返回null時才能退出循環(huán)。當(dāng)調(diào)用Looper的quit()/quitSafely()方法時巷折,Looper會調(diào)用MessageQueue的quit()方法來退出自己的消息隊列压鉴,當(dāng)消息隊列被標(biāo)記為退出后,它的next()方法就會返回null锻拘。
換句話說晴弃,就是如果looper不退出,它的loop()方法會一直循環(huán)下去逊拍。
Looper會調(diào)用MessageQueue的next方法來獲取新的消息上鞠,但next()是一個阻塞的方法,如果沒有新消息芯丧,next()方法會一直阻塞在那里芍阎,也導(dǎo)致loop()方法一直阻塞在那里。如果next()返回了新消息缨恒,Looper就會用msg.target.dispatchMessage(msg)方法分發(fā)消息谴咸。msg.target是一個Handler對象,用來識別這個消息應(yīng)該交給哪個Handler處理骗露。
當(dāng)一個消息被Looper讀取后岭佳,該消息會從消息隊列中銷毀。
這個過程可以用一個流程圖來簡單地概括:
有一點(diǎn)不能忘記,如果在子線程中創(chuàng)建了Looper,記得在使用完畢后一定要調(diào)用Looper的quit的方法退出Looper叶洞。
Looper提供了兩種退出方法:
- quit() : 直接退出looper鲫凶,但消息隊列中可能還有消息沒有處理。
- quitSafely() : 不直接退出looper衩辟,等消息隊列中所有的消息都處理完畢后才安全地退出螟炫。
如果在該線程的任務(wù)都已經(jīng)結(jié)束后沒有調(diào)用quit方法來終止消息循環(huán),這個子線程會一直處在運(yùn)行狀態(tài)艺晴,等待新消息的到來昼钻。而如果退出了Looper,這個子線程的代碼才算全部執(zhí)行完封寞,才會終止自己然评。
所以,出于資源的考慮钥星,一定要在不需要的時候終止Looper沾瓦。
到現(xiàn)在,這個過程已經(jīng)漸具雛形了谦炒。但還少了最后一個步驟分析:分發(fā)處理消息的過程贯莺。
Handler處理消息的過程
Handler的dispatchMessage()方法的源碼如下:
// Handler類
public void dispatchMessage(Message msg) {
if (msg.callback != null) { // 在Message類中有成員變量:Runnable callback;
handleCallback(msg);
} else {
if (mCallback != null) { // 在Handler類中有成員變量:final Callback mCallback;
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public interface Callback {
public boolean handleMessage(Message msg);
}
首先,檢查Message的callback是否為null宁改。如果不是缕探,則調(diào)用handleCallback()方法來處理消息。我們通過跟蹤該變量可以知道还蹲,callback是一個Runnable對象爹耗,實(shí)際上就是在用Handler的post方法時所傳遞的Runnable參數(shù)。直接調(diào)用callback.run()即可處理谜喊。
如果是null潭兽,再檢查mCallback是否為null。不為null就調(diào)用mCallback的handMessage()方法來處理消息斗遏。
如果mCallback也是null山卦,就直接用handleMessage()方法來處理消息。
這里出現(xiàn)了一個接口:Callback诵次。通過它可以直接用如下方式創(chuàng)建Handler對象:
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
看到這個用法账蓉,結(jié)合源碼的注釋:“Callback interface you can use when instantiating a Handler to avoid having to implement your own subclass of Handler”,可以用Callback接口來創(chuàng)建一個Handler實(shí)例逾一,而不用派生Handler的子類铸本。
在文章開頭舉例子的時候,我們說常見的使用Handler的方法遵堵,是繼承Handler類箱玷,并重寫handleMessage()方法來處理具體的消息。
當(dāng)我們不想派生子類的時候,就可以通過Callback實(shí)現(xiàn)汪茧。
Handler處理消息的過程也可以歸納為一個流程圖:
好了椅亚,以上就是安卓消息機(jī)制的全部內(nèi)容了限番。經(jīng)過之前的分析我們能夠看出舱污,其實(shí)Handler可以實(shí)現(xiàn)任何需要在兩個線程間進(jìn)行通信的功能,并不是只用來在子線程中通知主線程更新UI的弥虐,只是開發(fā)者比較常用它來更新UI而已扩灯。
而這種消息的傳遞也不只能用Handler實(shí)現(xiàn),只要一個標(biāo)志性變量在線程A中的改變能被線程B檢測到霜瘪,就能夠起到通知線程B的作用珠插,進(jìn)而使線程B作出響應(yīng)和改變。讀者們?nèi)绻信d趣甚至也可以仿照這種機(jī)制自己動手寫一個“Handler”出來颖对,只要具備了一個可以發(fā)送和處理消息的類捻撑,一個存儲消息的類,和一個不停循環(huán)檢查讀取消息并分發(fā)的類缤底,讓它們相互間協(xié)調(diào)配合默契顾患,就可以完成啦。
最后个唧,作者尚在學(xué)習(xí)江解,歡迎指摘~