Android中的消息機制主要指 Handler的運行機制 以及 MessageQueue,Looper的工做過程 ,三者相互協做,保證着消息的接收,發送,處理,執行。html
圖片來自郭神的《第一行代碼》java
先簡單的介紹一下 Android 中 消息機制你們庭的主要成員 :android
Handler : 是Android消息機制的上層接口,最爲你們經常使用,至關於Android消息機制的入口,咱們經過使用Handler
發送消息來引發消息機制的循環。一般用於:在子線程執行完耗時任務完後,更新UI。程序員
MessageQueue : 存儲 消息(Message) 對象的消息隊列,實則是單鏈表結構.安全
Looper : 用於無限的從 MessageQueue
中取出消息,至關於消息的永動機,若是有新的消息,則處理執行,若沒有,則就一直等待,堵塞。Looper** 所在的線程是 建立 Handler
時所在的線程。bash
主線程建立Handler時,會自動建立一個Looper,可是子線程並不會自動建立Looper多線程
ThreadLocal : 在每一個線程互不干擾的存儲,提供數據,以此來獲取當前線程的Looper
ide
ActivityThread : Android 的主線程,也叫UI線程,主線程被建立時 自動初始化主線程的 Looper
對象。oop
看完《Android藝術開發探索》 這本書的第10章以後我也才明白post
這個很簡單了,若是爲不一樣的線程訪問同一UI元素加上鎖機制,那咱們程序員寫相關代碼的時候會變得超級麻煩。。。 改個UI還得考慮它是否是已經被別的線程佔用了,被佔用了,還得讓那個線程釋放鎖。。。線程再多一點的話,大大地加大了程序員地工做量.
並且加上鎖機制無疑會因爲線程堵塞地緣由下降訪問UI的效率,幀率下降,體驗也會不友好。
讓UI元素只能再主線程訪問就會省下不少事,建立一個Handler
就好了。
下面從總體概述一下 消息機制的整個工做過程 :
Handler
建立時會採用當前線程的 Looper
來構建內部的消息循環系統,若是Handler在子線程,則一開始是沒有Looper
對象的(解決方法稍後介紹),主線程ActivityThread
默認有一個Looper。
Handler
建立完畢,經過 post
方法傳入Runnable
對象,或者經過sendMessage(Message msg)
發送消息。
post()
方法裏也是經過調用send()
實現的
send()
方法被調用後,調用 MessageQueue的enqueueMessage()方法將消息發送到消息隊列中,等待被處理。
Looper
對象運行在Handler
所在的線程,從MessageQueue消息隊列
中不斷地取出消息,處理,因此業務邏輯(一般是更新UI)就運行在Looper的線程中。
接下來從局部來分析消息機制的每一個成員。
ThreadLocal是一個線程內部的數據存儲類,經過它能夠在指定的線程中得到存儲數據,得到數據,線程之間的ThreadLocal相互獨立,且沒法得到另外一個線程的TheadLocal.
相對整個程序來講,每一個線程的ThreadLocal是局部變量。
相對一個線程來講,線程內的ThreadLocal是線程的全局變量
ThreadLocal是一個泛型類,能夠存儲任意類型的對象。
示例:
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>();
mThreadLocal.set(true);
System.out.println("#Main Thread : ThreadLocal " + mThreadLocal.get());
new Thread( new Runnable() {
@Override
public void run() {
mThreadLocal.set(false);
System.out.println("#1 Thread : ThreadLocal " + mThreadLocal.get());
}
}).start();
new Thread( new Runnable() {
@Override
public void run() {
System.out.println("#2 Thread : ThreadLocal " + mThreadLocal.get());
}
}).start();
}
}
複製代碼
咱們在主線程建立一個 泛型爲Boolean
的ThreadLocal,並.set(True)
,而後在第一個子線程中.set(False)
,在第二個子線程中不作修改,直接打印。 能夠看到,在不一樣的線程中得到的值也不一樣。
輸出 :
#Main Thread : ThreadLocal true
#1 Thread : ThreadLocal false
#2 Thread : ThreadLocal null
複製代碼
首先每一個線程內部都維護着一個ThreadLocalMap
對象
Thread.Java
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
複製代碼
這個ThraedLocalMap 與Map相似,一個線程內能夠有多個ThreadLocal類型變量,因此經過ThreadLocalMap <ThreadLocal<?> key, Object value>
.保存着多個<ThreadLocal , 任意類型對象>鍵值對。
看一下ThreadLocal的set()
方法實現 :
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
複製代碼
先是得到當前線程的ThreadLocalMap
對象,map.set(this,value) 設置了我這個ThreadLocal存儲的值.
get()
方法實現 :
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */
public T get() {
Thread t = Thread.currentThread();//得到當前線程
ThreadLocalMap map = getMap(t);//根據根據得到它的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//得到<k,v>鍵值對
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;//經過<k,v>得到值
return result;
}
}
return setInitialValue();
}
複製代碼
通常,當某些數據是以線程爲做用域,而且不一樣的線程具備不一樣的數據副本時,能夠考慮用ThreadLocal
對於Handler
,它想要得到當前線程的Looper
,而且Looper
的做用域就是當前的線程,不一樣的線程具備不一樣的Looper
對象,這時可使用ThreadLocal。
複雜邏輯下的對象的傳遞,若是想要一個對象貫穿着整個線程的執行過程,可採用Threadlocal讓此對象做爲該線程的全局對象。
以單鏈表的形式,存儲着Handler
發送過來的消息,再來一張圖加深印象
主要包含兩個操做:
enqueueMessage(Message msg,long when)
,像隊列插入一個消息,這裏爲了節省篇幅,就不上源碼,貼上源碼鏈接,MessageQueue.enqueueMessage()next()
從無限循環隊列中取出消息,並從消息隊列中刪除。MessageQueue.next()雖然它叫作消息隊列
,但內部實際上是以單鏈表的結構存儲,有利於插入,刪除的操做。
它的主要做用就是 不停地從消息隊列中 查看是否有新的消息,若是有新的消息就會馬上處理,沒有消息就會堵塞。
持有MessageQueue
的引用,而且會在構造方法中初始化
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製代碼
前面說到主線程本身會建立一個Looper
對象,因此咱們在主線程使用Handler
的時候直接建立就能夠了。
可是在子線程使用Handler的話,就須要咱們手動建立Looper
了,
示例:
new Thread() {
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
複製代碼
prepare()
源碼以下:
/** Initialize the current thread as a looper. 77 * This gives you a chance to create handlers that then reference 78 * this looper, before actually starting the loop. Be sure to call 79 * {@link #loop()} after calling this method, and end it by calling 80 * {@link #quit()}. 81 */
82 public static void prepare() {
83 prepare(true);
84 }
85
86 private static void prepare(boolean quitAllowed) {
87 if (sThreadLocal.get() != null) {
88 throw new RuntimeException("Only one Looper may be created per thread");
89 }
90 sThreadLocal.set(new Looper(quitAllowed));
91 }
複製代碼
能夠看到最終是調用了 此 Looper 所在線程的 **ThreadLocal.set()**方法,存了一個Looper對象進去。
除了prepare()
,還有一些其餘方法,咱們也須要知道
loop() : 啓動消息循環,,只有當Looper調用了loop()以後,整個消息循環才活了起來
prepareMainLooper() : 給主線程建立Looper對象
getMainLooper() : 得到主線程的Looper對象
quit() : 通知消息隊列,直接退出消息循環,不等待當前正在處理的消息執行完,quit以後,再向消息隊列中發送新的消息就會失敗( Handler的send()方法就會返回false )
public void quit() {
mQueue.quit(false);
}
複製代碼
quitSafety() : 經過消息隊列,再也不接收新的消息,等當前的消息隊列中的消息處理完就退出。
public void quitSafely() {
mQueue.quit(true);
}
複製代碼
下面分析loop()
的實現:
/** 119 * Run the message queue in this thread. Be sure to call 120 * {@link #quit()} to end the loop. 121 */
122 public static void loop() {
123 final Looper me = myLooper();
124 if (me == null) {
125 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
126 }
127 final MessageQueue queue = me.mQueue;
128
129 ...//省略部分代碼
133 //從這裏開啓無限循環,直到 沒有消息
134 for (;;) {
135 Message msg = queue.next(); // might block
136 if (msg == null) {
137 // No message indicates that the message queue is quitting.
138 return;
139 }
140
141 // This must be in a local variable, in case a UI event sets the logger
142 Printer logging = me.mLogging;
143 if (logging != null) {
144 logging.println(">>>>> Dispatching to " + msg.target + " " +
145 msg.callback + ": " + msg.what);
146 }
147
148 msg.target.dispatchMessage(msg);
149 ...//省略部分代碼
166 }
167 }
複製代碼
在 for 循環裏 :
msg.target.dispatchMessage(msg);
,target是發送此消息的 Hander對像,通知Handler調用dispatchMessage()
來接收消息。Handler的主要工做就是 發送消息,接收消息。
發送消息的方式有post(),send(),不過post()方法最後仍是調用的send()方法
發送消息的過程:
send類型的發送消息方法有不少,而且是嵌套的
sendMessage()
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
複製代碼
sendMessageDelayed()
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
複製代碼
sendMessageAtTime()
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);
}
複製代碼
三種send的發送消息方式,最後都會經過enqueueMessage()
來通知消息隊列 插入這條新的消息。
Handler.enqueueMessage
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//調用消息隊列的enqueueMessage()
}
複製代碼
接收消息的過程
接收消息由dispatchMessage(Message msg)
爲入口
dispatchMessage()
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
這裏的callback
是咱們調用 post(Runnable runnalbe) 時傳入的Runnable
對象,若是咱們傳入了Runnable
對象
就會執行Runnable的
run方法:
private static void handleCallback(Message message) {
message.callback.run();
}
複製代碼
若是沒有經過post
傳Runnable
,就會看建立Handler時的構造方法中有沒有傳Runnable
參數,傳了的話由mCallback
存儲。
這個mCallback
是Handler內部的一個接口
public interface Callback {
public boolean handleMessage(Message msg);
}
複製代碼
若是構造Handler時也沒有傳Runnable
對象,最終會執行handleMessage(msg)
,這個 方法就是咱們建立handler時重寫的handleMessage()方法.
參考資料: Android藝術開發探索
(完~)