前言:俗話說的好,萬丈高樓平地起,學好數理化,走遍天下都不怕;只有先打好基礎的狀況下,才能夠更深刻地去學習知識;今天帶你們設計一下Handler的機制,會經過一種特別的的方式,來進行講解,但願能夠對你有所幫助;android
1,首先提出一個疑問,爲何面試官都很愛問Handler相關的知識?由於Handler機制是Android一個很是重要的通訊機制,不少框架的底層實現都是經過Handler來更新UI的;git
2,那麼問題來了,Android在哪裏使用Handler消息機制,爲何要使用這個Handler?下面咱們一步步的來深刻了解,揭開Handler的真面目吧!github
1,在Android的世界,存在着無數的線程,其中有一個特殊的線程, 被賦予了特殊的使命,它就是傳說中的主線程;爲何說它特殊呢?由於它掌握着能夠更新UI(視圖)的權力;那麼問題來了,其它的線程說我也要更新UI呢?總不能也給這個線程賦予更新UI的權力吧,若是每一個線程都去更新UI,很容易就亂套了,那怎麼解決呢?很簡單,交由主線程去更新就好了;那麼怎麼通知主線程去更新呢?請繼續往下看;面試
2,Google工程師造了主線程以後,相應的也創造了一些工具給主線程使用,是什麼工具呢?就是相似通信工具的Handler,沒錯,Handler就通信工具,至於通信工具用來作什麼,很簡單,就是用來發送消息️;當其它的線程須要更新UI的時候,只須要打個電話給主線程,把你要傳遞的消息(Message)告訴它(主線程)就好了;️bash
3,通信工具是怎麼運做的呢?當通信工具發送消息的時候,會把消息傳送到最近的通訊基站(MessageQueue),這時候還有一個角色出場了,就是通訊衛星(Looper),通訊衛星是一個無時無刻不在工做的辛勤勞動者,通訊衛星(Looper)的做用就是從通訊基站(MessageQueue)裏面取出消息,而後發送給主線程的通信工具,這時候主線程家裏的(callBack)接受到其它線程發來的消息後,更新UI;多線程
固然上面純屬我的舉例,用於加深理解!框架
下面咱們來看看Handler的設計!工具
假如你Google工程師,你面臨着一個難題,多個線程在一塊兒工做,你們都有更新UI的需求,時不時的你更新一下UI,我再更新一下UI,這時候有可能會致使更新的東西被別的線程給覆蓋了,這就是多個線程同時操做會出現的問題;那這個問題要怎麼解決呢?oop
既然你們都有更新UI的需求,那爲什麼不統一管理,使用一個線程來更新UI便可,其它線程須要更新UI的時候,告訴那個能夠更新UI的線程,讓它來更新就行,這樣就能夠避免上面那種狀況的出現;既然有了思路,那接下來要怎麼實現呢?請繼續往下看!post
先規定一個線程爲主線程,賦予它能夠更新UI的權力,而後再設計一個能夠發送消息的通信工具,將其命名爲Handler,Handler的職責就是發送消息,通知能夠更新UI的線程,咱們須要更新UI了;那麼設計出來的效果以下,裏面有一個能夠發送消息的方法enqueueMessage();
public class Handler {
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis);
}
}
複製代碼
如今有了發送消息的工具,接下來還得設計另外一個工具用來接收消息;
在設計以前,有一個疑惑,若是有多個線程同時要操做更新UI的話,那確定是沒辦法同時操做;好比說:有好幾我的要去售票處買火車票,可是售票處只有一個窗口,沒辦法作到同時給那麼多人售票,這時候就可讓人們進行排隊,有序的購票,這樣你們都能買到票,又不會出現混亂的狀況;這裏咱們也能夠採用這種方法來解決問題;設計一個消息隊列MessageQueue,讓Handler發送的消息,在這裏進行排隊,設計以下:
public final class MessageQueue {
Message mMessages
// 存儲消息的方法
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
Message p = mMessages;
if (p == null) {
// 將第一個消息添加進隊列
msg.next = p;
mMessages = msg;
} else {
Message prev;
for (;;) {
prev = p;
p = p.next;
}
// 將傳進來的消息連接到上一個消息的後面,進行排隊
prev.next = msg;
}
}
return true;
}
}
複製代碼
消息隊列(MessageQueue)裏面主要設計了一個鏈表的結構,讓進來的消息能夠連接到上一個消息的後面,從而達到排隊的目的,也就是enqueueMessage()方法;
再看一下消息的本體結構設計,設計了一個排隊的標記next,標記誰排在它的後面,用於連接隊列:
public final class Message implements Parcelable {
public int what;
...
Message next;
}
複製代碼
如今有了發送消息的Handler,也有了接收消息的隊列(MessageQueue),那麼還須要一個能夠把消息從消息隊列裏面取出來的工具;
在設計以前,有一個疑問:線程有可能會發送消息,有可能不會發送消息,我並不知道消息隊列裏面是否有消息;
由此得知,這個工具須要不停的查看消息隊列裏面有沒有消息,有的話就將其取出來,避免耽誤了其它線程更新UI的需求,這就要求這個工具須要時刻不停的工做着,那麼就將其設計爲不停工做的輪循器(Looper);
輪循器裏面設計了一個無限循環的機制,能夠不停的從消息隊列裏面取出消息,那麼設計出來的效果以下:
public final class Looper {
public static void loop() {
...
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
// 不停的循環從隊列裏面取出消息來;
for (;;) {
...
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
// 回調給主線程
msg.target.dispatchMessage(msg);
} finally {
...
}
...
}
}
}
複製代碼
輪循器Looper裏面有一個無限循環的方法,能夠一直從消息隊列(MessageQueue)裏面取出消息出來;
這時候又有疑問了,輪循器(Looper)取出來的消息要怎麼發給主線程?
咱們能夠經過設計一個回調,來將這個消息回調給主線程;
接下來在Handler裏面設計一個接口,能夠將Looper發送的消息回調到主線程,經過handleMessage()方法,這個方法最終是經過Handler裏的dispatchMessage來進行回調;
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
...
}
}
複製代碼
Handler裏的dispatchMessage()則設計由Looper來進行調用;
那麼到這裏一個完整的消息機制就設計完了,可是這樣子就結束了嗎?然而事情並無這麼簡單,且聽我細細道來!
上面設計的只是給主線程使用的一套消息機制,一個輪循器(Looper) + 隊列(MessageQueue),那麼當其它線程之間也須要進行通信呢?總不能都使用主線程的消息機制吧,這樣會亂套的;
那這樣的話就給每個線程設計單獨一個輪循器(Looper) + 隊列(MessageQueue),用於自身的通信;那麼問題又來了,這麼多線程,對應這這麼多個輪循器(Looper) + 隊列(MessageQueue),要怎麼管理也是一個問題;這麼多個消息機制,哪一個是屬於本身線程的,哪一個是屬於其它線程的,必需要劃分好界限才行,否則就會出現a線程想發送消息給b線程,結果發送到c線程去了,這樣子就混亂了;
既然如此,那麼咱們將線程和輪循器(Looper) + 隊列(MessageQueue)綁定起來,經過設計一個管理器來管理這些輪循器(Looper) + 隊列(MessageQueue),將每個線程對應的每個輪循器(Looper) + 隊列(MessageQueue)作好一一對應關係;
那麼咱們就設計一個線程管理類ThreadLocal來管理這些關係,看一下設計出來的效果:
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
}
複製代碼
設計兩個方法,經過鍵值對的方式來進行存儲與取出,以線程爲鍵,以Looper爲值,將其存儲起來;
而後在Looper裏面將這個ThreadLocal設置爲全局惟一的變量,這樣其它的線程隨時能夠經過ThreadLocal來獲取本身的Looper;
效果以下:
public final class Looper {
// 全局惟一的變量,線程隨時能夠獲取到
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void loop() {
...
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
// 不停的循環從隊列裏面取出消息來;
for (;;) {
...
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
} finally {
...
}
...
}
}
}
複製代碼
如今每一個模塊的工具都已經設計完成了,接來下要讓這幾個模塊協同工做,組裝成一套完成的消息機制;
Handler:發送消息;
MessageQueue:接收消息的消息隊列;
Looper:輪循器,不斷的從消息隊列裏面取出消息;
Message:消息結構體,支持鏈表結構;
ThreadLocal:管理線程的輪循器(Looper);
這裏咱們把輪循器(Looper) + 隊列(MessageQueue)進行綁定,爲一個線程進行服務; 而後在Handler裏面經過ThreadLocal來獲取本身的Looper,這樣發送的消息就會進入Looper裏的消息隊列(MessageQueue),而後在經過Looper的循環,經過Handler裏的接口回調給相應的線程;
讓咱們看看設計後的結構圖:
到這裏一套完整的消息機制就設計完成了;
下面來看一下運做的關係圖:
Handler的設計很巧妙,源碼裏的細節不少,我就不貼出來了,這裏只是講一下Handler結構設計,建議能夠本身跟着源碼去走一遍,用於加深理解!
1,每個線程都有一個本身的輪循器Looper和消息隊列MessageQueue,用於接收其它線程發來的消息,每個線程都有本身惟一的Looper和MessageQueue;
2,Handler能夠無限建立,由於建立的Handler會和線程的Looper進行綁定;
3,Handler發送消息後,會在消息隊列裏面進行排隊,並不會當即被響應到;
4,Handler的消息隊列MessageQueue裏的消息存在滯後性,所以會存在內存泄露的風險;
5,Handler的Message是鏈表的結構,用於在消息隊列MessageQueue裏面進行排隊;
6,ThreadLocal用於管理線程的Looper,用來保證其一一對於的關係;
兄dei,若是個人文章對你有幫助的話,點個讚唄️,也能夠關注一下個人Github和博客;
歡迎和我溝通交流技術;