——個人平時筆記,看到的同學(xué)歡迎指正錯誤,文中多處摘錄于各大博主與書籍精華
1、定義:Android的消息機制主要是指Handler的運行機制,Handler是同一個進程中線程間的通信機制,主要作用就是將一個任務(wù)切換到指定的線程中去執(zhí)行,Handler并不是專門用于更新UI的,只是這樣的特性正好可以用來解決在子線程中無法訪問UI的矛盾,才常被開發(fā)者用來更新UI。
Handler消息機制流程(深入探討Android異步精髓Handler):每一個handler的創(chuàng)建都必須有Looper.prepare()->new Handler()-> sendMessage()->MessageQueue->Looper.loop()->handlerMessage(),但是在UI線程即主線程中,系統(tǒng)會自動調(diào)用Looper.prepareMainLooper()方法創(chuàng)建主線程的Looper(Looper.prepare()與Looper.loop())和消息隊列MessageQueue。
>1、Looper.prepare():做準備工作,為當(dāng)前線程創(chuàng)建一個Looper。在其內(nèi)部源代碼中,每一個Looper.prepare()創(chuàng)建一個Looper,Looper構(gòu)造方法內(nèi)又會初始化一個MessageQueue消息隊列和一個線程Thread(當(dāng)前線程),這也是為什么很多人說的一個Handler只能持有一個MessageQueue的原因。并且由源碼可知一個線程對應(yīng)一個Looper也只有一個Looper.prepare(),否則會拋出異常。在Looper.prepare()內(nèi)會調(diào)用sThreadLocal.set(new Looper(quitAllowed)),這個操作會創(chuàng)建一個新的Looper并壓入sThreadLocal數(shù)組中。至于Looper,它在Android的消息機制中擔(dān)負著消息輪詢的角色,它會不間斷地查看MessageQueue中是否有新的未處理的消息,若有則立刻處理,若無則進入阻塞等待。
>2、handler.sendMessage():調(diào)用handler.sendMessage()、handler.sendMessageAtTime()等方法發(fā)送消息時,在其內(nèi)部源碼中都會調(diào)用enqueueMessage(MessageQueue queue, Message msg,long uptimeMillis)方法,并在其方法內(nèi)部處理后調(diào)用queue.enqueueMessage(msg,uptimeMillis)方法將消息插入到消息集MessageQueue隊列中。MessageQueue隊列是遵從先進先出的原則,然而有個例外,如果調(diào)用handler.sendMessageAtFrontOfQueue()方法會直接將uptimeMillis入隊列的延遲時間參數(shù)設(shè)置為0,所以msg消息會直接被插入到消息隊列最頂部,待取出消息時又會按優(yōu)先頂部取出原則取出。在enqueueMessage(MessageQueue queue, Message msg,long uptimeMillis)方法內(nèi)部已經(jīng)為每一個msg指定了target標簽,原文“msg.target =this;”這里的this就是當(dāng)前handler,所以為什么調(diào)用sendMessage()能夠準確的發(fā)送到對應(yīng)的handlerMessage()接收。
>3、queue.enqueueMessage(msg, uptimeMillis):將消息發(fā)送并插入到MessageQueue消息隊列中,uptimeMillis是發(fā)送的延遲時間參數(shù)。
Handler可以通過post()、postAtTime()、postDelayed()、postAtFrontOfQueue()等方法發(fā)送消息,這幾個方法均會執(zhí)行到sendMessageAtTime(Message msg, long uptimeMillis)方法,除了postAtFrontOfQueue()之外會執(zhí)行sendMessageAtFrontOfQueue(getPostMessage(r))方法。但是sendMessageAtTime方法與sendMessageAtFrontOfQueue方法在源碼里最終均是調(diào)用queue.enqueueMessage(msg, uptimeMillis)方法。
MessageQueue消息隊列它的內(nèi)部存儲結(jié)構(gòu)并不是真正的隊列,而是采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲消息列表(單鏈表在插入和刪除操作上效率比較高)。
>4、Looper.loop():輪詢讀取消息。消息的出隊執(zhí)行者,在loop()內(nèi)部會發(fā)起一個死循環(huán)不斷遍歷MessageQueue內(nèi)部輪詢,取出消息Message msg =queue.next(),next()取出一條消息并將其從消息隊列中移除,直到取出的消息不為空時,才調(diào)用msg.target.dispatchMessage(msg)將消息發(fā)送到熟悉的handleMessage(msg)中接收(而msg.target=this;this即Handler本身),Handler類中的handleMessage()實現(xiàn)了消息的回調(diào),使用回調(diào)徹底完成線程切換。在需要的時候或事情完成后可以調(diào)用quit()方法停止消息的輪詢,此時next()會返回null,loop()方法會結(jié)束,Looper也跟著退出,Looper退出后線程也會跟著終止。
補充:在主線程ActivityThread中也是會有一個Looper.loop()不斷循環(huán),但是queu.next()也是阻塞和休眠保證main()不會執(zhí)行完畢切在等待的時間能夠給GC時間回收,因為main()執(zhí)行完畢會拋出?thrownewRuntimeException("Main thread loop unexpectedly exited");異常

>5、Handler在哪個線程創(chuàng)建,也就與哪個線程綁定,一個線程可以持有多個Handler。Handler的主要作用是將一個任務(wù)切換到某個指定的線程中去執(zhí)行;當(dāng)handler通過一系列的post或send方法發(fā)送消息到達目標線程的MessageQueue(消息隊列是指定的目標線程持有的)則此時也就切換了線程。線程是默認沒有Looper的,如果需要使用Handler就必須為線程創(chuàng)建Looper。參考:android之handler切換線程終極篇
在介紹的最后,我對handler機制的全過程的總結(jié)為:
1.首先Looper.prepare()會在當(dāng)前線程保存一個looper對象,并且會維護一個消息隊列messageQueue,而且規(guī)定了messageQueue在每個線程中只會存在唯一的一個。
2.Looper.loop()方法會使線程進入一個無限循環(huán),不斷地從消息隊列中獲取消息,之后回調(diào)msg.target.disPatchMessage方法。
3.我們在實例化handler的過程中,會先得到當(dāng)前所在線程的looper對象,之后得到與該looper對象相對應(yīng)的消息隊列。
4.當(dāng)我們發(fā)送消息的時候,即handler.sendMessage或者handler.post,會將msg中的target賦值為handler自身,之后加入到消息隊列中。
5.在第三步實現(xiàn)實例化handler的過程中,我們一般會重寫handlerMessage方法(使用post方法需要實現(xiàn)run方法),而這些方法將會在第二步中的msg.target.disPatchMessage方法中被回調(diào),從而實現(xiàn)了message從一個線程到另外一個線程的傳遞。
查看源碼可以看到如下解釋,當(dāng)我們創(chuàng)建Handler對象時,就與該線程和該線程的消息隊列相綁定,如果未與當(dāng)前線程和線程隊列綁定就無法正常執(zhí)行事件的分發(fā)處理。我們在主線程創(chuàng)建Handler,就會與主線程相綁定,Handler對象隱式的持有外部對象的引用,該外部對象通常是指Activity,故要避免內(nèi)存溢出。
>6、ThreadLocal并不是線程,是一個數(shù)據(jù)存儲類,它的作用是可以在每個線程中存儲數(shù)據(jù)。Handler創(chuàng)建的時候會采用當(dāng)前線程的Looper來構(gòu)造消息循環(huán)系統(tǒng),而ThreadLocal可以在不同的線程中互不干擾地存儲并提供數(shù)據(jù),通過ThreadLocal可以輕松獲取每個線程的Looper。ThreadLocal<T> mThreadLocal = new ThreadLocal<T>();mThreadLocal能夠存儲當(dāng)前自己線程下的值,多個線程間的數(shù)據(jù)不干擾。不同線程訪問同一個ThreadLocal的get方法,ThreadLocal內(nèi)部會從各自的線程中取出一個數(shù)組,然后再從數(shù)組中根據(jù)當(dāng)前ThreadLocal的索引去查找出對應(yīng)的value值。很顯然,不同線程中的數(shù)組是不同的,這就是為什么通過ThreadLocal可以在不同的線程中維護一套數(shù)據(jù)的副本并且彼此互不干擾。
小結(jié):
1.一個線程對應(yīng)一個Looper
2.一個Looper對應(yīng)一個MessageQueue消息隊列
3.一個線程對應(yīng)一個MessageQueue消息隊列
4.線程,Looper,MessageQueue消息隊列三者一一對應(yīng)
5.post的一系列方法最終也是通過send的一系列方法來實現(xiàn)的如:handler.postAtTime(),handler.sendMessageAtTime()
6.一個線程可以有多個Handler

>7、避免handler造成內(nèi)存泄漏:
①先說handler導(dǎo)致activity內(nèi)存泄露的原因:
handler發(fā)送的消息在當(dāng)前handler的消息隊列中,如果此時activity finish掉了,那么消息隊列的消息依舊會由handler進行處理,若此時handler聲明為內(nèi)部類(非靜態(tài)內(nèi)部類),我們知道內(nèi)部類天然持有外部類的實例引用,那么就會導(dǎo)致activity無法回收,進而導(dǎo)致activity泄露。
②為何handler要定義為static?
因為靜態(tài)內(nèi)部類不持有外部類的引用,所以使用靜態(tài)的handler不會導(dǎo)致activity的泄露
③為何handler要定義為static的同時,還要用WeakReference 包裹外部類的對象?
這是因為我們需要使用外部類的成員,可以通過"activity. "獲取變量方法,更新UI等,如果直接使用強引用,顯然會導(dǎo)致activity泄露。
/**
* handler為何會導(dǎo)致內(nèi)存泄露:
* 如果handler為內(nèi)部類(非靜態(tài)內(nèi)部類),那么會持有外部類的實例,若在handler.sendMessage的時候,activity finish掉了,那么此時activity將無法得到釋放,如果申明handler為靜態(tài)內(nèi)部類,則不會含有外部類的引用,但是需要在handler中更新UI(注意此時handler為static),則需要引入一個activity引用,顯然必須是弱引用,之所以使用弱引用,是因為handler為static,使用activity的弱引用來訪問activity對象內(nèi)的成員
*/
private static class MyHandler extends Handler {
private WeakReference<HandlerOOMActivity> mWeakReference;
public MyHandler(HandlerOOMActivity activity)
{
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(mWeakReference!=null)
{
Activity activity = mWeakReference.get();
if(activity!=null){
//handler消息處理
activity.tv.setText("我是更改后的文字");
}
}
}?????
在外部Activity釋放消息隊列,清除Message和Runnable:
@Overrideprotected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);?
}
.