安卓在子線程中不能更新UI,因此大部分狀況下,咱們須要藉助Handler切換到主線程中去更新消息.而消息機制(即Handler那一坨)在安卓中的地位很是很是重要,咱們須要詳細瞭解其原理.這一塊,學過不少次,可是,我以爲仍是再學億次,寫成博客輸出.但願對你們有所幫助,有一些新的感悟.html
ThreadLocal主要是能夠在不一樣的線程中存儲不一樣的數據,它是將數據存儲在線程內部的,其餘線程沒法訪問.對於同一個ThreadLocal對象,不一樣的線程有不一樣的數據,這些數據互不干擾.好比Handler機制中的Looper,Looper的做用域是線程,ThreadLocal能夠將Looper存儲在線程中,而後其餘線程是沒法訪問到這個線程中的Looper的,只供當前線程本身內部使用.java
下面簡單舉個例子:linux
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final ThreadLocal<Integer> INTEGER_THREAD_LOCAL = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//設置ThreadLocal裏面的數據爲1
INTEGER_THREAD_LOCAL.set(1);
//獲取ThreadLocal裏面的數據
Log.w(TAG, "主線程" + INTEGER_THREAD_LOCAL.get());
new Thread(new Runnable() {
@Override
public void run() {
//獲取ThreadLocal裏面的數據,可是須要注意的是,這裏獲取的數據是子線程中數據,由於沒有進行初始化,這裏獲取到的數據是null
Log.w(TAG, "線程1 " + INTEGER_THREAD_LOCAL.get());
}
}, "線程1").start();
}
}
複製代碼
我先在主線程中將INTEGER_THREAD_LOCAL
的值設置爲1(至關於主線程中的INTEGER_THREAD_LOCAL
值爲1),而後再開啓子線程並在子線程中獲取INTEGER_THREAD_LOCAL
的值.由於子線程中沒有給INTEGER_THREAD_LOCAL
附值,因此是null.android
2019-05-19 11:12:54.353 12364-12364/com.xfhy.handlerdemo W/MainActivity: 主線程1
2019-05-19 11:12:54.353 12364-12383/com.xfhy.handlerdemo W/MainActivity: 線程1 null
複製代碼
須要注意到的是INTEGER_THREAD_LOCAL
是final static
的,這裏的ThreadLocal是同一個對象,可是在主線程中獲取到的數據和在子線程中獲取到的數據卻不同. 這裏的demo也就證實了: ThreadLocal在不一樣的線程中存儲的數據,互不干擾,相互獨立.數組
咱們從ThreadLocal的set方法開始深刻下去(通常讀源碼是從使用處的API開始,這樣會更輕鬆地理清思路)bash
public void set(T value) {
//1. 獲取當前線程
Thread t = Thread.currentThread();
//2. 獲取當前線程的threadLocals屬性,threadLocals是Thread類裏面的一個屬性,是ThreadLocalMap類型的,專門用來存當前線程的私有數據,這些數據由ThreadLocal維護
ThreadLocalMap map = getMap(t);
//3. 第一次設置值的時候map確定是爲null的,初始化了以後map纔不爲null
//第一次會去createMap()
if (map != null)
//4. 將當前ThreadLocal對象和value的值存入map中
map.set(this, value);
else
//4. 這裏將初始化map,而且將value值放到map中.
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
複製代碼
ThreadLocal在設置數據的時候,首先是獲取當前線程的threadLocals屬性,threadLocals是Thread類裏面的一個屬性,是ThreadLocalMap類型的,專門用來存當前線程的私有數據,這些數據由ThreadLocal來維護的. 當第一次設置值的時候,須要初始化map,並將value值放入map中.下面來看一下這部分代碼app
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
複製代碼
//下面是ThreadLocalMap的代碼
/** * The table, resized as necessary. * table.length MUST always be a power of two. * table是ThreadLocalMap裏面存儲數據的地方,若是在數組長度不夠用的時候,會擴容. 存儲的方式是靠hash值爲數組的索引,將value放到該索引處. */
private Entry[] table;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化table數據數組
table = new Entry[INITIAL_CAPACITY];
//計算hash值->存儲數據的索引
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//將value值存入map中,key爲ThreadLocal
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
複製代碼
能夠看到createMap方法中就是初始化ThreadLocalMap,而ThreadLocalMap的底部實際上是一個數組,它是利用hash值來計算索引,而後存儲數據到該索引處的方式.框架
此處須要注意的是,咱們能夠看到ThreadLocal是將數據存儲到Thread的一個threadLocals屬性上面,這個threadLocals每一個線程獨有的,那麼存儲數據確定互不干擾啊,完美.less
Handler中的消息隊列,也就是MessageQueue.從名字能夠看出這是一個隊列,可是它的底層倒是單鏈表結構.由於鏈表結構比較適合插入和刪除操做.這個MessageQueue的查詢就是next()方法,它的查詢伴隨着刪除.async
消息隊列的插入,對應着的是enqueueMessage方法
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
....
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//若是 1. 鏈表爲空 || 2. when是0,表示當即須要處理的消息 || 3. 當前須要插入的消息比以前的第一個消息更緊急,在更短的時間內就須要處理
//知足上面這3個條件中的其中一個,那麼就是插入在鏈表的頭部
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
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();
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) {
// 激活消息隊列去獲取下一個消息 這裏是一個native方法
nativeWake(mPtr);
}
}
return true;
}
複製代碼
核心內容爲消息列表的插入,也就是鏈表的插入,插入數據的時候是有必定規則的,當知足下面這3個條件中的其中一個,那麼就是插入在鏈表的頭部
其餘狀況則是插入在鏈表中的合適的位置,找到一個合適的時間點.
MessageQueue的next方法,也就是獲取下一個消息,這個方法可能會阻塞,當消息隊列沒有消息的時候.直到有消息,而後就會被喚醒,而後繼續取消息.
可是這裏的阻塞是不會ANR的,真正致使ANR的是由於在handleMessage方法中處理消息時阻塞了主線程過久的時間.這裏的緣由,後面再解釋.
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//當消息隊列爲空時,這裏會致使阻塞,直到有消息加入消息隊列,纔會恢復
//這裏是native方法,利用的是linux的epoll機制阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//這裏比較關鍵 取鏈表頭部,獲取這個消息
// 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();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
.....
}
......
}
}
複製代碼
核心內容就是取消息隊列的第一個元素(即鏈表的第一個元素),而後將該Message取出來以後,將它從消息隊列中刪除.
Looper在消息機制中主要扮演着消息循環的角色,有消息來了,Looper就取出來,分發.沒有消息,Looper就阻塞在那裏,直到有消息爲止.
先來看一下,Looper的構造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製代碼
這個構造方法是私有化的,只能在內部調用,直接在裏面初始化了MessageQueue和獲取當前線程.構造方法只會在prepare方法中被調用.
public static void prepare() {
prepare(true);
}
//sThreadLocal是用`static final`修飾的,意味着sThreadLocal只有一個,可是它卻能夠在不一樣的線程中存儲不一樣的Looper,妙啊
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
//若是說當前線程以前初始化過ThreadLocal,裏面有Looper,那麼就報錯
//意思就是prepare方法只能調用一次
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//初始化ThreadLocal,將一個Looper存入其中
sThreadLocal.set(new Looper(quitAllowed));
}
private static Looper sMainLooper;
//這個方法是主線程中調用的,準備主線程的Looper.也是隻能調用一次.
public static void prepareMainLooper() {
//先準備一下
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//將初始化以後的Looper賦值給sMainLooper,sMainLooper是static的,多是爲了方便使用吧
sMainLooper = myLooper();
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
複製代碼
prepare方法的職責是初始化ThreadLocal,將Looper存儲在其中,一個線程只能有一個Looper,不能重複初始化.sThreadLocal是用static final
修飾的,意味着sThreadLocal只有一個,可是它卻能夠在不一樣的線程中存儲不一樣的Looper.並且官方還提供了主線程初始化Looper的專用方法prepareMainLooper.主線程就是主角,還單獨把它的Looper存到靜態的sMainLooper中.
下面開始進入Looper的核心方法loop(),咱們知道loop方法就是死循環不斷得從MessageQueue中去取數據.看看方法中的一些細節.
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */
public static void loop() {
//1. 首先是獲取當前線程的Looper 穩,不一樣的線程,互不干擾
final Looper me = myLooper();
//2. 若是當前線程沒有初始化,那確定是要報錯的
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//3. 取出當前線程Looper中存放的MessageQueue
final MessageQueue queue = me.mQueue;
.....
for (;;) {
//4. 從MessageQueue中取消息,固然 這裏是可能被阻塞的,若是MessageQueue中沒有消息能夠取的話
Message msg = queue.next(); // might block
//5. 若是消息隊列想退出,而且MessageQueue中沒有消息了,那麼這裏的msg確定是null
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
.....
//6. 注意啦,這裏開始分發當前從消息隊列中取出來的消息
msg.target.dispatchMessage(msg);
......
}
}
複製代碼
loop方法很是重要,它首先取到當前線程的Looper,再從Looper中獲取MessageQueue,開啓一個死循環,從MessageQueue的next方法中獲取新的Message.可是在next方法調用的過程當中是可能被阻塞的,這裏是利用了linux的epoll機制.取到了消息以後分發下去.分發給Handler的handleMessage方法進行處理. 而後又開始了一個新的輪迴,繼續取新的消息(也多是阻塞在那裏等).
下面來看一下消息的分發
//Message裏面的代碼
//Message裏的target其實就是發送該消息的那個Handler,666
Handler target;
//下一個消息的引用
Message next;
複製代碼
//Handler裏面的代碼
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
兄弟萌,它來啦,仍是那個熟悉的handleMessage方法,在Looper的loop方法中由Message本身經過Message裏面的target(handler)調用該Handler本身的handleMessage方法.完成了消息的分發. 若是這裏有Callback的話,就經過Callback接口分發消息.
Handler的做用其實就是發送消息,而後接收消息.Handler中任何的發送消息的方法最後都會調用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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
sendMessageAtTime方法很簡單,其實就是將消息插入MessageQueue.而在Message插入MessageQueue的過程以前,先將Handler的引用存入Message中,方便待會兒分發消息事件,機智機智!
在安卓消息機制中,ThreadLocal拿來存儲Looper,而MessageQueue是存儲在Looper中的.因此咱們能夠在子線程中經過主線程的Handler發送消息,而Looper(主線程中的)在主線程中取出消息,分發給主線程的Handler的handleMessage方法.
咱們知道ActivityThread其實就是咱們的主線程,首先咱們來看一段代碼,ActivityThread的main方法:
public static void main(String[] args) {
......
//注意看,在main方法的開始,在主線程中就準備好了主線程中的Looper,存入ThreadLocal中.因此咱們平時使用Handler的時候並無調用prepare方法也不會報錯
Looper.prepareMainLooper();
......
//直接在主線程中調用了loop方法,而且陷入死循環中,不斷地取消息,不斷地處理消息,無消息時就阻塞.
//嘿,你還別說,這裏這個方法還必需要死循環下去纔好,否則就會執行到下面的throw new RuntimeException語句報出錯誤
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼
主線程一直處在一個Looper的loop循環中,有消息就會去處理.無消息,則阻塞.
有什麼騷東西非要進行死循環才能處理呢?首先咱們想一想,既然ActivityThread開啓了Looper的loop,那麼確定有Handler來接收和處理消息,咱們一探究竟:
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING = 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
public static final int SHOW_WINDOW = 105;
public static final int HIDE_WINDOW = 106;
public static final int RESUME_ACTIVITY = 107;
public static final int SEND_RESULT = 108;
public static final int DESTROY_ACTIVITY = 109;
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int NEW_INTENT = 112;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
public static final int STOP_SERVICE = 116;
...
}
複製代碼
名場面,上面就是API 28之前ActivityThread.H的老樣子,爲何是API 28之前?由於在API 28中重構了H類,把100到109這10個用於Activity的消息,都合併爲159這個消息,消息名爲EXECUTE_TRANSACTION(抽象爲ClientTransactionItem,有興趣瞭解的看這裏)。
在H類中定義了不少消息類型,包含了安卓四大組件的啓動和中止.ActivityThread經過ApplicationThread與AMS進行進程間通訊,AMS完成ActivityThread的請求後會回調ApplicationThread中的Binder方法,而後ApplicationThread會向H發送消息,H收到消息就開始在主線程中執行,開始執行諸如Activity的啓動中止等動做,以上就是主線程的消息循環模型.
既然咱們知道了主線程是這樣啓動Activity的,那麼咱們是否是能夠搞點騷操做???俗稱黑科技的插件化:咱們Hook掉H類的mCallback對象,攔截這個對象的handleMessage方法。在此以前,咱們把插件中的Activity替換爲StubActtivty,那麼如今,咱們攔截到handleMessage方法,再把StubActivity換回爲插件中的Activity.當前這只是API 28以前的操做,更多詳情請看這裏
既然主線程中的main方法內調用了Looper的loop方法不斷地死循環取消息,並且當消息隊列爲空的時候還會被阻塞.那爲何主線程中當沒有消息的時候怎麼不卡呢?
此處引出一國外網友的回答,短小精湛.問題回答原地址
簡短版答案: nativePollOnce方法是用來等待下一個消息可用時的,下一個消息可用則不會再繼續阻塞,若是在這個調用中花費的時間很長,那你的主(UI)線程沒有真正的工做要作,而且等待下一個事件處理。不必擔憂阻塞問題。
完整版的答案: 由於主線程負責繪製UI和處理各類事件,因此Runnable有一個處理全部這些事件的循環。循環由Looper管理,其工做很是簡單:它處理MessageQueue中的全部消息。消息被添加到隊列中,例如響應輸入事件,幀渲染回調甚至您本身的Handler.post調用。有時主線程沒有工做要作(即隊列中沒有消息),這可能發生在例如剛完成渲染單幀後(線程剛剛繪製了一幀並準備好下一幀,只需等待一段時間)。 MessageQueue類中的兩個Java方法對咱們來講頗有趣:Message next()和boolean enqueueMessage(Message,long)。消息next(),顧名思義,接收並返回隊列中的下一條消息。若是隊列爲空(而且沒有任何內容能夠返回),則該方法調用native void nativePollOnce(long,int),該塊將阻塞,直到添加新消息。此時你可能會問nativePollOnce如何知道什麼時候醒來。這是一個很是好的問題。將Message添加到隊列時,框架會調用enqueueMessage方法,該方法不只會將消息插入隊列,還會調用native static void nativeWake(long),若是須要喚醒隊列的話。 nativePollOnce和nativeWake的核心魔力發生在native(其實是C ++)代碼中。 Native MessageQueue使用名爲epoll的Linux系統調用,該調用容許監視IO事件的文件描述符。 nativePollOnce在某個文件描述符上調用epoll_wait
,而nativeWake寫入描述符,這是IO操做之一,epoll_wait
等待。而後內核從等待狀態中取出epoll等待線程,而且線程繼續處理新消息。若是您熟悉Java的Object.wait()和Object.notify()方法,您能夠想象nativePollOnce是Object.wait()和NativeWake for Object.notify()的粗略等價物,由於它們的實現徹底不一樣:nativePollOnce使用epoll,Object.wait()使用futex Linux調用。值得注意的是,nativePollOnce和Object.wait()都不會浪費CPU週期,由於當線程進入任一方法時,它會因線程調度而被禁用。若是這些方法實際上浪費了CPU週期,那麼全部空閒應用程序將使用100%的CPU,加熱並下降設備的速度。
翻譯的不是很好,英語好的同窗仍是看原版吧,,,,,,,,,