Handler使用
首先來熟悉一下Handler的四種使用方式,如果比較熟悉可以直接跳過:
- 通過sendMessage消息機(jī)制來發(fā)送
sendEmptyMessage(int);//發(fā)送一個空的消息
sendMessage(Message);//發(fā)送消息秽澳,消息中可以攜帶參數(shù)
sendMessageAtTime(Message, long);//未來某一時間點(diǎn)發(fā)送消息
sendMessageDelayed(Message, long);//延時Nms發(fā)送消息
接收數(shù)據(jù)
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
String data = (String) msg.obj;
textView.setText("接收數(shù)據(jù):" + data);
break;
default:
break;
}
}
};
發(fā)送數(shù)據(jù)
new Thread(new Runnable() {
@Override
public void run() {
Message msg = new Message();
msg.what = 0;
msg.obj = "測試數(shù)據(jù)";
mHandler.sendMessage(msg);
}
}).start();
- 使用Post方式留量;
post(Runnable);//提交計(jì)劃任務(wù)馬上執(zhí)行
postAtTime(Runnable, long);//提交計(jì)劃任務(wù)在未來的時間點(diǎn)執(zhí)行
postDelayed(Runnable, long);//提交計(jì)劃任務(wù)延時Nms執(zhí)行
new Thread(new Runnable() {
@Override
public void run() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
tvContent.setText("接收數(shù)據(jù):" + str);
}
}, 3000);
}
}).start();
其實(shí)傳遞Runnable也是最終轉(zhuǎn)換成message通過sendMessage來發(fā)送涨椒,將Runnable賦給message的callback砂沛,
這個callback在Handler取消息的時候會先進(jìn)行判斷,如果不為空就直接調(diào)用了础拨,如果為空才會調(diào)用handMessage方法
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
- 使用runOnUiThread方式掠抬;
runOnUiThread其實(shí)是調(diào)用的post
new Thread(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
tvContent.setText("接收數(shù)據(jù):" + str);
}
});
}
}).start();
- 通過View的Post方式吼野;
也是調(diào)用的Handler的post方法
new Thread(new Runnable() {
@Override
public void run() {
tvContent.post(new Runnable() {
@Override
public void run() {
tvContent.setText("接收數(shù)據(jù):" + str);
}
});
}
}).start();
Handler組成
Handler的四大成員:
發(fā)送方式 | 功能介紹 |
---|---|
Message | 主要功能是進(jìn)行消息的封裝,同時可以指定消息的操作形式 |
Looper | 消息循環(huán)泵两波,用來為一個線程跑一個消息循環(huán)瞳步,每一個線程最多只可以擁有一個 |
MessageQueue | 就是一個消息隊(duì)列,存放消息的地方每一個線程最多只可以擁有一個 |
Handler | 消息的處理者腰奋,handler 負(fù)責(zé)將需要傳遞的信息封裝成Message单起,發(fā)送給Looper,繼而由Looper將Message放入MessageQueue中劣坊。當(dāng)Looper對象看到MessageQueue中含有Message嘀倒,就將其廣播出去。該handler 對象收到該消息后局冰,調(diào)用相應(yīng)的handler 對象的handleMessage()方法對其進(jìn)行處理 |
需要注意的點(diǎn)是每個線程只有一個Looper和一個MessageQueue测蘑,一個線程中可以有多個Handler,每個Handler會處理各自的消息康二,互相之間沒有影響碳胳,也就是多個Handler的發(fā)送和接收之間值多對多的關(guān)系。
Handler原理分析
首先來看下主線程和子線程創(chuàng)建Handler的不同
- 主線程
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
- 子線程
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Looper.loop();
}
}).start();
發(fā)現(xiàn)主線程直接建立即可赠摇,而子線程需要使用Looper.prepare()和Looper.loop方法固逗,這個為啥浅蚪?帶著疑問藕帜,接著往下看烫罩。
從Looper.prepare()開始
當(dāng)Looper.prepare()被調(diào)用時,發(fā)生了什么洽故?
public static void prepare() {
prepare(true); //最終其實(shí)執(zhí)行的是私有方法prepare(boolean quitAllowed)中的邏輯
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) { //先嘗試獲取是否已經(jīng)存在一個Looper在當(dāng)前線程中贝攒,如果有就拋個異常。
//這就是為什么我們不能在一個Thread中調(diào)用兩次Looper.prepare()的原因时甚。
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed)); //首次調(diào)用的話隘弊,就創(chuàng)建一個新的Looper。
}
//Looper的私有構(gòu)造函數(shù)
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); //創(chuàng)建新的MessageQueue荒适,稍后在來扒它梨熙。
mThread = Thread.currentThread(); //把當(dāng)前的線程賦值給mThread。
}
}
可以看到首次調(diào)用prepare的時候會創(chuàng)建一個Looper實(shí)例放進(jìn)去刀诬,創(chuàng)建Looper實(shí)例的時候會創(chuàng)建一個與之關(guān)聯(lián)的MessageQueue咽扇,那么這個sThreadLoacal是什么呢? sThreadLocal是個靜態(tài)的ThreadLocal<Looper> 實(shí)例(在Android中ThreadLocal固定為Looper)。那么陕壹,Looper.prepare()既然是個靜態(tài)方法质欲,Looper是如何確定現(xiàn)在應(yīng)該和哪一個線程建立綁定關(guān)系的呢?
來看看ThreadLocal的get()糠馆、set()方法嘶伟。
public void set(T value) {
Thread t = Thread.currentThread(); //同樣先獲取到當(dāng)前的線程
ThreadLocalMap map = getMap(t); //獲取線程的ThreadLocalMap
if (map != null)
map.set(this, value); //儲存鍵值對
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread(); //重點(diǎn)啊又碌!獲取到了當(dāng)前運(yùn)行的線程九昧。
ThreadLocalMap map = getMap(t); //取出當(dāng)前線程的ThreadLocalMap。這個東西是個重點(diǎn)毕匀,前面已經(jīng)提到過耽装。忘了的同學(xué)在前面再看看。
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
//可以看出期揪,每條線程的ThreadLocalMap中都有一個<ThreadLocal,Looper>鍵值對掉奄。綁定關(guān)系就是通過這個鍵值對建立的。
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
重點(diǎn)來了:每個線程有一個與之關(guān)聯(lián)的Looper凤薛,這個Looper是用ThreadLoacaMap來存儲的姓建,ThreadLoacaMap存儲的就是ThreadLocal對象,而Android中的ThreadLocal就是Looper缤苫,所以存的就是Looper速兔,并且ThreadLocalMap是在每線程中都私有的,這就保證的并發(fā)時的線程安全活玲,這是典型的以空間來換時間涣狗,而我們熟悉的synchronized是以時間換空間谍婉。
創(chuàng)建Handler
平時我們都使用new Handler()來在一個線程中創(chuàng)建Handler實(shí)例,但是它是如何知道自己應(yīng)該處理那個線程的任務(wù)呢
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) { //可以看到镀钓,最終調(diào)用了這個方法穗熬。
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(); //重點(diǎn)啊丁溅!在這里Handler和當(dāng)前Thread的Looper綁定了唤蔗。Looper.myLooper()就是從ThreadLocale中取出當(dāng)前線程的Looper。
if (mLooper == null) {
//如果子線程中new Handler()之前沒有調(diào)用Looper.prepare()窟赏,那么當(dāng)前線程的Looper就還沒創(chuàng)建妓柜。就會拋出這個異常。
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //賦值Looper的MessageQueue給Handler涯穷。
mCallback = callback;
mAsynchronous = async;
}
上面代碼中有mLooper = Looper.myLooper()棍掐,這就是Handler和Looper進(jìn)行了綁定,然后Handler就可以給Looper發(fā)消息了拷况,我們調(diào)用Handler的sendMessage方法也就是往Looper的MessageQueue里邊發(fā)消息
Looper.loop()
我們都知道作煌,在Handler創(chuàng)建之后,還需要調(diào)用一下Looper.loop()蝠嘉,這樣才能循環(huán)起來最疆,那Looper是怎樣把消息準(zhǔn)確的送到Handler中處理
public static void loop() {
final Looper me = myLooper(); //這個方法前面已經(jīng)提到過了,就是獲取到當(dāng)前線程中的Looper對象蚤告。
if (me == null) {
//沒有Looper.prepare()是要報(bào)錯的努酸!
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //獲取到Looper的MessageQueue成員變量,這是在Looper創(chuàng)建的時候new的杜恰。
//這是個Native方法获诈,作用就是檢測一下當(dāng)前線程是否屬于當(dāng)前進(jìn)程。并且會持續(xù)跟蹤其真實(shí)的身份心褐。
//在IPC機(jī)制中舔涎,這個方法用來清除IPCThreadState的pid和uid信息。并且返回一個身份逗爹,便于使用restoreCallingIdentity()來恢復(fù)亡嫌。
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) { //重點(diǎn)(敲黑板)!這里是個死循環(huán)掘而,一直等待抽取消息挟冠、發(fā)送消息。
Message msg = queue.next(); // 從MessageQueue中抽取一條消息袍睡。至于怎么取的知染,我們稍后再看。
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
final long traceTag = me.mTraceTag; //取得MessageQueue的跟蹤標(biāo)記
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); //開始跟蹤本線程的MessageQueue中的當(dāng)前消息斑胜,是Native的方法控淡。
}
try {
msg.target.dispatchMessage(msg); //嘗試分派消息到和Message綁定的Handler中
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag); //這個和Trace.traceBegin()配套使用嫌吠。
}
}
final long newIdent = Binder.clearCallingIdentity(); //what?又調(diào)用這個Native方法了掺炭。這里主要是為了再次驗(yàn)證辫诅,線程所在的進(jìn)程是否發(fā)生改變。
msg.recycleUnchecked(); //回收釋放消息竹伸。
}
}
從上面的分析可以知道泥栖,當(dāng)調(diào)用了Looper.loop()之后簇宽,線程就就會被一個for(;;)死循環(huán)阻塞勋篓,每次等待MessageQueue的next()方法取出一條Message才開始往下繼續(xù)執(zhí)行。然后通過Message獲取到相應(yīng)的Handler (就是target成員變量)魏割,Handler再通過dispatchMessage()方法譬嚣,把Message派發(fā)到handleMessage()中處理。
MessageQueue
MessageQueue是一個用單鏈的數(shù)據(jù)結(jié)構(gòu)來維護(hù)消息列表钞它,現(xiàn)在又產(chǎn)生一個疑問拜银,MessageQueue的next()方法是如何阻塞住線程的呢?
Message next() {
//檢查loop是否已經(jīng)為退出狀態(tài)遭垛。mPrt是Native層的MessageQueue的地址尼桶。通過這個地址可以和Native層的MessageQueue互動。
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0; //時間標(biāo)記锯仪,當(dāng)且僅當(dāng)?shù)谝淮潍@取消息時才為0泵督。因?yàn)樗谒姥h(huán)外面啊庶喜!
for (;;) {
//這是一個Native的方法小腊。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) { //鎖住MessageQueue
//獲取當(dāng)前的系統(tǒng)時間,用于后面和msg.when進(jìn)行比較久窟。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; //獲得當(dāng)前MessageQueue中的第一條消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) { //這個判斷的意義在于只有到了Message應(yīng)該被發(fā)送的時刻才去發(fā)送秩冈,否則繼續(xù)循環(huán)。
//計(jì)算下一條消息的時間斥扛。注意最大就是Integer.MAX_VALUE入问。
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else { //應(yīng)該發(fā)送一條消息了。
// 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(); //轉(zhuǎn)換消息標(biāo)記為使用過的
return msg; //返回一條消息給Looper稀颁。
}
} else {
// 如果取到的Message為null芬失,將時間標(biāo)記設(shè)置為-1。
nextPollTimeoutMillis = -1;
}
}
}
可以看到峻村。MessageQueue在取消息(調(diào)用next())時麸折,會進(jìn)入一個死循環(huán),直到取出一條Message返回粘昨。這就是為什么Looper.loop()會在queue.next()處等待的原因
上面方法中出現(xiàn)了一個nativePollOnce(ptr, nextPollTimeoutMillis);函數(shù)的調(diào)用垢啼。線程會被阻塞在這個地方窜锯,實(shí)際上是阻塞在了底層的Looper的epoll_wait()這個地方等待喚醒呢。nativeWake()芭析,通過這個來進(jìn)行喚醒喚醒锚扎,這個方法是在enqueueMessage中調(diào)用的
下面看下enqueueMessage的代碼:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) { //沒Handler調(diào)用是會拋異常的啊
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) { //鎖住MessageQueue再往里添加消息。
msg.markInUse(); //切換Message的使用狀態(tài)為未使用馁启。
msg.when = when; //我們設(shè)置的延遲發(fā)送的時間驾孔。
//經(jīng)過下面的邏輯,Message將會被“儲存”在MessageQueue中惯疙。實(shí)際上翠勉,Message在MessageQueue中的儲存方式,
//是使用Message.next逐個向后指向的單鏈表結(jié)構(gòu)來儲存的霉颠。比如:A.next = B, B.next = C...
Message p = mMessages; //嘗試獲取當(dāng)前Message
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 如果為null对碌,說明是第一條。
msg.next = p;
mMessages = msg; //設(shè)置當(dāng)前的Message為傳入的Message蒿偎,也就是作為第一條朽们。
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//不滿足作為第一條Message的條件時,通過下面的逐步變換诉位,將它放在最后面骑脱。這樣便把Message“儲存”到MessageQueue中了。
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;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
為什么主線程創(chuàng)建Handler不用Looper
因?yàn)橹骶€程在ActivityThread苍糠,也就是主線程初始化的時候通過Looper.prepareMainLooper()建立好了對應(yīng)的Looper
注意ActivityThread并沒有繼承Thread叁丧,它的Handler是繼承Handler的私有內(nèi)部類H.class。在H.class的handleMessage()中椿息,它接收并執(zhí)行主線程中的各種生命周期狀態(tài)消息歹袁。UI的16ms的繪制也是通過Handler來實(shí)現(xiàn)的。也就是說寝优,主線程中的所有操作都是在Looper.prepareMainLooper()和Looper.loop()之間進(jìn)行的条舔。
ThreadLocal說明
這個在上面已經(jīng)說過了,因?yàn)槭欠浅5闹匾Ψ栽谶@里再強(qiáng)調(diào)一遍孟抗。Android中ThreadLocal的表現(xiàn)形式就是Looper,我們在Looper.prepare()中會創(chuàng)建一個Looper實(shí)例钻心,并將其存入ThreadLocalMap中凄硼,而ThreadLoacalMap是屬于特定線程的,這樣不僅保證的線程安全捷沸,同時也將Looper和對應(yīng)的線程關(guān)聯(lián)起來了摊沉,Looper創(chuàng)建的時候也會創(chuàng)建一個對應(yīng)的MessageQueue
HandlerThread
先來看一個HandlerThread使用的例子:
HandlerThread thread = new HandlerThread("MyHandlerThread");
thread.start();
mHandler = new Handler(thread.getLooper());
mHandler.post(new Runnable(){...});
再看下HandlerThread的源碼
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
}
它的代碼比較短,我們主要來看一下它的run()方法痒给,我們發(fā)現(xiàn)它和普通Thread不同之處在于它在run()方法內(nèi)創(chuàng)建了一個消息隊(duì)列说墨,然后來通過Handler的消息的方式來通知HandlerThread執(zhí)行下一個具體的任務(wù)骏全。由于HandlerThread的run()方法內(nèi)Looper是個無限循環(huán),所以當(dāng)我們不需要使用HandlerThread的時候可以通過qiut()的方法來終止尼斧。
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
quit()實(shí)際上就是讓run()內(nèi)的Looper停止循環(huán)
removemessages
這個是用來移除消息隊(duì)列中對應(yīng)what類型的數(shù)據(jù)姜贡,當(dāng)有postMessageDelay消息進(jìn)入倒計(jì)時的時候可以使其停止,但是如果設(shè)置的時間達(dá)到了棺棵,那么也是沒有作用的
先來看下源碼楼咳,下面是Handler的調(diào)用
// Handler.java
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
}
他會接著調(diào)用MessageQueue里邊的removeMessages
// MessageQueue.java
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// 原代碼注釋,Remove all messages at front.
// 根據(jù)上面?zhèn)魅氲膮?shù)烛恤,p!=null成立母怜,p.target == h成立,object == null 成立
// 即此處可等同為p.what == what
// 此處代碼即找到第一個p.what == what的消息并將其移除
while (p != null && p.target == h && p.what == what
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
// 此處即為移除找到第一個后的Message
while (p != null) {
Message n = p.next;
if (n != null) {
// 同上棒动,此處也可以等同為p.what == what
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
由源碼中可以看出糙申,MessageQueue將Message.what與函數(shù)傳入的what相同的Message從隊(duì)列中移除
一個線程多個Handler
一個線程可以建立多個Handler宾添,但是一個線程只有一個Looper船惨,也只有一個MessageQueue,多個Handler發(fā)送的消息會都放到一個MessageQueue中進(jìn)行遍歷缕陕。重點(diǎn):同一個Handler只能處理自己發(fā)送給Looper的那些Message粱锐,多個Handler之間是互不干擾的,也就是多對多的關(guān)系扛邑。移除相同類型消息的時候怜浅,其他Handler也不會受到影響
尊重作者,尊重原創(chuàng)蔬崩,參考文章:
Handler實(shí)例:https://www.cnblogs.com/whoislcj/p/5590615.html
Handler原理:http://www.reibang.com/p/8862bd2b6a29
HandlerThread:http://www.reibang.com/p/5b6c71a7e8d7
多對多關(guān)系:https://blog.csdn.net/u011573355/article/details/50735604