1.Handler概述
Handler是Android的一套消息傳遞機制。
為什麼要使用Handler呢?隻有一點原因:在界面可視之後,子線程不能更新UI,是以子線程需要一種手段來通知UI線程資料已更新,這個手段就是Handler。
1.1 Handler的組成
它有四個非常重要的對象來完成這套機制:
-
負責消息的發送和處理Handler
-
消息隊列,負責存放消息MessageQueue
-
消息載體Message
-
負責消息的輪詢Looper
上面的四個東西都是處于同一個線程,在一個線程裡面實作了Handler機制。
這裡畫一個圖來看看他們之間的關系:

這是一個非常簡單了解的版本,下面來解讀一下
- 任何一個線程隻有一個Looper,任意一個Looper裡面隻有一個MessageQueue
- MessageQueue維護的是一個 Message連結清單,裡面的Message有明确的指向
- 一個線程可以持有有多個Handler
2. Handler機制源碼
2.1 Handler構造函數
先來看下Handler的構造函數:
// Handler.java
private static final boolean FIND_POTENTIAL_LEAKS = false;
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
IMessenger mMessenger;
public interface Callback {
public boolean handleMessage(Message msg);
}
public Handler(Callback callback, boolean async) {
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();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Handler主要實作上面兩種構造方法。分别對 mLooper、mQueue、mCallback、mAsynchronous進行了傳參。
- mCallback:
類型,是一個實作了Callback
的對象,由構造時外部代入,可以說是回調函數,或者說Handler機制的出口。handleMessage()
- mLooper:
類型,每個Handler都會持有綁定一個LooperLooper
- mQueue :
類型,作為Handler中消息隊列的存在,它是Looper的消息變量,是以會從 mLooper中取出。MessageQueue
- mAsynchronous :是否異步
上面兩個構造函數的差別是,前者沒有在傳參時帶入Looper,是以目前的 Looper為
Looper.myLooper()
。為了搞懂這個方法、對象是什麼意義?我們需要先知道Looper。
2.2 Looper從哪裡冒出來的
首先每個線程隻有一個Looper,在Handler機制的場景下,我們要得到就是 主線程(即UI線程)的那個Looper。
那麼主線程的Looper是怎麼來的,在 應用程式程序的啟動的學習中(如果沒學過請參考 《Android進階解密-應用程式程序啟動過程》),在應用程式程序被建立時,會建立UI線程ActivityThread,并調用它的 main():
// ActivityThread.java
public static void main(String[] args) {
...
Looper.prepareMainLooper(); // 1
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
Looper.loop(); // 1
...
}
注釋1:建立一個 主線程Looper。
注釋2:開啟這個Looper。
先暫且不看第二個方法,去看看第一個方法裡面做了什麼:
// Looper.java
public static void prepareMainLooper() {
prepare(false); // 1
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper(); // 2
}
}
注釋1:調用了
prepare(flase)
注釋2:同樣的,也調用了
myLooper()
,并把傳回結果指派給了 sMainLooper,聽名字就知道它是 主線程的Looper。
來看看prepare方法做什麼:
// Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
到這個方法裡,我們看到了 UI線程的Looper,就是在這個地方(
prepare()
)建立的。
它通過構造函數,持有了目前UI線程的引用mThread,并且建構了一個新的
MessageQueue
mQueue,且為非異步。
并把這個 UI線程Looper 通過
ThreadLocal.set()
設定到了 sThreadLocal中:
// ThreadLocal.java
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
的東西,而set方法就是把 (threadLocal, UiLooper)作為鍵值對存進去的,也就是說 ThreadLocalMap是一個存放
ThreadLocal - Looper
的集合,我們可以看看 ThreadLocalMap這個類,它是ThreadLocal的内部類:
// ThreadLocal.java
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
// 1
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 2
private Entry[] table;
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 3
int i = key.threadLocalHashCode & (len-1);
// 4
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { // 5
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value); // 6
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
來解析一個 ThreadLocalMap:
注釋1:一個
Entry
包含一個弱引用的 ThreadLocal作為key,一個Object作為value,這個Object我們前面帶進來的就是Looper。
注釋2:表明 ThreadLocalMap的資料結構是一個
Entry
數組
注釋3:ThreadLocalMap的set方法借鑒了HashMap得做法,元素的存儲位置是
key的hash值mod哈希桶的長度
注釋4、注釋6:通過注釋3,拿到存儲位置 i,如果i位置存在有效元素,也就是說發生了哈希沖突,這個時候 i取
nextIndex(i, len)
,即i 的下一個下标,然後拿新的i去判斷這個位置有沒有元素,直到找到為空的元素,然後放入這個Looper。
注釋5:如果i下标存在元素,但是 它的key和拿來存放資料的 key是一樣的,則覆寫value。
到這裡我們就知道ThreadLocalMap是做什麼的了:
ThreadLocalMap總結:
TheradLocalMap
存放
ThreadLocal - Looper
的鍵值對,其中ThreadLocal是弱引用,它采用hash的方式存取,效率比較高,如果出現了hash沖突,則采用線性探測的方法解決。它維護了目前ThreadLocal所持有的Looper。
回到
ThreadLocal.set()
中,UI線程的Looper和Looper的成員變量 sThreadLocal被作為 k-v已經被存放到這個 ThreadLocalMap中了。然後又回到
prepareMainLooper()
的注釋2中: sMainLooper = myLooper(),這裡的重點是
myLooper()
方法,我們來看看這個方法做了什麼:
// Looper.java
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
myLooper() 是傳回
ThreadLocal.get()
:
// ThreadLocal.java
public T get() {
Thread t = Thread.currentThread(); // 1
ThreadLocalMap map = getMap(t); // 2
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 3
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
注釋1、2:擷取到目前線程的
ThreadLocalMap
。
注釋3:得到 ThreadLocalMap中目前 ThreadLocal所綁定的Looper。
是以
ThreadLocal.get()
擷取的是目前線程的Looper
而在目前場景中,我們拿到的是主線程的Looper。
這裡也就得出了Handler構造函數中Looper從哪裡來的結論:
每個線程隻有一個Looper,如果不在Handler構造函數中帶入Looper,那麼Looper就是:Handler所線上程的Looper。
我們知道Looper是怎麼出生的,但是我們還不知道它是做什麼的,在這節的開頭,我們通過
prepareMainLooper()
建立了UI線程的Looper後,我們又通過
Looper.loop()
開啟了Looper的輪詢,這個方法非常重要,我們需要深入它。
2.3 Loop.loop()
來看下 loop()代碼:
// Looper.java
public static void loop() {
final Looper me = myLooper(); // 1
...
final MessageQueue queue = me.mQueue; // 2
...
for (;;) { // 3
Message msg = queue.next(); // 4
if (msg == null) { // 5
// No message indicates that the message queue is quitting.
return;
}
...
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
...
try {
msg.target.dispatchMessage(msg); // 6
} finally {
...
}
...
msg.recycleUnchecked();
}
}
上面濾去了不重要的代碼。
注釋1:拿到目前線程的Looper(即ui線程的Looper)
注釋2:從Looper中拿出
MessageQueue
注釋3:開啟死循環
注釋4:從
MessageQueue.next()
中擷取一個 Message出來
注釋5:如果取出的Message為null,則跳出死循環
注釋6:根據 Message的target來通過
dispatchMessage(msg)
來發送這個消息。
也就是說
Looper.loop()
開啟了一個死循環,來處理其 MessageQueue裡面的每一個Message。
直到 Message為null了才跳出循環,而英文注釋表示 當隻有在
MessageQueue
退出的時候 Message才為空,而MessageQueue的生命和線程一樣,也就是說:線程終止了,這個循環才會結束。
是以也進而驗證了,這個類為什麼叫Looper(循環者,不是某冠軍上單)。
這裡有兩個問題,也是面試的時候面試官喜歡問的:
(1)為什麼開了死循環,App卻感受不了卡頓,也沒有ANR?
逆推回去,既然App沒有卡頓,也就是說
MessageQueue
有源源不斷的message供主線程處理,這是因為像 AMS、WMS這些SystemServer程序在程式運作時會一直提供功能的支援,通過Binder機制,向主程序的主線程中發送消息,是以 Looper的死循環一直是工作狀态,是以并不會導緻卡頓。
(2)那這樣一直開着死循環,不會很占CPU資源嗎?
這裡就要繼續看代碼了,我們看看上述循環中
MessageQueue.next()
做了什麼.
2.4 MessageQueue.next()
因為 next()代碼有點長,是以把其中死循環的部分分成三個部分進行講解:
Part1 無消息處理時挂起
// MessageQueue.java
private native void nativePollOnce(long ptr, int timeoutMillis);
Message next() {
...
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) { // 1
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis); // 2
....
}
}
注釋1中:可以看到的,
next()
也開了個死循環,WTF?什麼鬼,loop()已經是死循環,這裡還來套一個死循環了???
注釋2:調用
nativePollOnce(ptr, nextPollTimeoutMillis)
,這個方法非常滴重要,它是一個 native方法,我這裡就不再深入到 JNI層去看C的代碼了,我這邊用大白話來解釋這個代碼的作用:
在主線程打開Looper循環前,會打開Android系統底層的一個I/O管道(
Linux epoll
),如果你身邊有個背景人員,你可以問他關于
epoll
的知識,epoll做的事情就是監視檔案描述符的I/O事件,它是 事件驅動模型,換言之,它就是一個NIO。在沒有事件時主線程挂起,有事件時主線程喚醒處理。
關于C++層的源碼可以參考下這一篇:Android 中 MessageQueue 的 nativePollOnce。
函數中傳入了 nextPollTimeoutMillis,有點像
Thread.sleep(mills)
。這個nextPollTimeoutMillis會在延時任務中起到作用。
這裡也就回答了上節末尾的問題,為什麼開死循環不會特别占用CPU的資源,是因為在沒有消息的時候主線程已經挂起,有消息時才會喚醒。
Part2 傳回一個消息
// MessageQueue.java
...
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; // 1
...
if (msg != null) {
if (now < msg.when) { // 2
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next; // 3
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
....
這段代碼比較好了解,不過我們要先知道
MessageQueue
裡面維護的是一個
Message
連結清單
注釋1:拿到目前的
Message
mMessage。
注釋2:判斷 msg是否是延時任務,如果是的話則不處理,并更新
nextPollTimeoutMillis
的時間
注釋3:如果 msg不是延時任務,則把 mMessage指向目前 msg在連結清單中的下一個。然後return目前的msg。
Part3 IdleHandler
...
synchronized (this) {
....
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size(); // 1
}
if (pendingIdleHandlerCount <= 0) { // 2
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; // 3
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 4
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
keep = idler.queueIdle(); // 5
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0; // 6
nextPollTimeoutMillis = 0; // 7
}
}
這段代碼涉及到了
IdleHandler
,它是MessagQueue的内部類,我們先來看看他是做啥的:
// MessagQueue.java
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
public static interface IdleHandler {
boolean queueIdle();
}
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
可以發現它是一個接口,通過
addIdleHandler()
可以添加實作了
queueIdle()
方法的對象進來,儲存在
mIdleHandlers
的List中。
我們回到part3的代碼中來解析一下:
注釋1:如果pendingIdleHandlerCount < 0 并且目前沒有Message或者是延時任務,則把
mIdleHandlers
的大小指派給pendingIdleHandlerCount 。
注釋2:如果指派過後的pendingIdleHandlerCount <= 0,則說明目前線程沒有可以執行的任務,那麼continue,在下一個循環中将目前線程挂起。
注釋3:如果mPendingIdleHandlers為null,則new一個IdleHandler出來指派給它,并将其轉換成一個 數組。
注釋4:周遊 mPendingIdleHandlers
注釋5:取出其中的每個
IdleHandler
并執行它的
queueIdle()
方法。
注釋6、注釋7:清空 pendingIdleHandlerCount 和 nextPollTimeoutMillis 。因為如果是延時任務,早就已經continue了。另外一個事情就是,如果第一次打開死循環,Message連結清單是空的,這時候主線程可以做些别的事情(就是
IdleHandler
),做完之後,之後的循環就不會再去做這樣的操作了(除非我們自己加IdleHandler進來)。
到這裡
MessageQueue.next()
就解析完了,這裡做一個總結,它大概做了三件事情:
- 開啟死循環
- 如果目前沒有Message可以處理,則調用
挂起主線程,不占用CPU資源。 等到有消息可以處理時喚醒主線程。nativePollOnce()
- 如果存在非延時的消息,則把該消息傳回到Looper中。
- 如果是循環的第一次,可能會沒有消息,這個時候可以 處理
的消息,做一些别的事情,這相當于提升了性能,不讓主線程一上來就因為沒有Message而挂起,然後下個循環又馬上被喚醒。IdleHandler
2.5 Message是如何被加入到MessageQueue中的?
在上一節中,我把MessaqeQueue和Messaqe的關系籠統的概括為: MessageQueue中維護了一個Message連結清單。
僅僅這樣了解是不夠的,我們需要搞清楚,一個Message是怎麼放到 MessageQueue中的。入口方法就是我們常用的
Handler.sendMessage()
或者
Handler.sendMessageDelayed()
還是
Handler.postDelay()
,他們最終都會調用
sendMessageAtTime()
:
// Handler.java
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);
}
這個方法會調用
enqueueMessage()
,然後傳入目前 Handler的
MessageQueue()
mQueue,我們在開篇講過,mQueue是構造函數中就被建立,它是傳入的 Looper的MessageQueue。
// Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // 1
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis); // 2
}
注釋1:把
msg.target = this
,就是說這個消息發送給目前Handler的。
注釋2:調用
MessageQueue.enqueueMessage()
,傳入消息體和延時時間。這個動作就是把消息加入到 MQ中了。
來看一下加入過程:
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages; :
boolean needWake;
if (p == null || when == 0 || when < p.when) { //1
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { // 2
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;
}
注釋1:如果目前的Message是非延時任務,或者說比清單最後一個Message的執行時間要快,那麼它就插隊,插到前面去。
注釋2:否則的話,就往清單後邊找,找到 末尾或者第一個Message執行時間比目前的要晚的,然後插進去。
這段代碼很簡單,要麼目前任務 插到前面,要麼插到後面最早執行的位置。
到這裡,一個Message的入隊就講完了。
2.6 關于消息分發 msg.target.dispatchMessage(msg)
回到Looper中,在死循環裡,它會調用
msg.target.dispatchMessage(msg)
, 我們知道,msg.target是發送消息的Handler,那麼這裡調用了 目标Handler的
dispatchMessage()
把Message發送出去,來看看這個方法:
// Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); // 1
} else {
if (mCallback != null) { // 2
if (mCallback.handleMessage(msg)) { // 3
return;
}
}
handleMessage(msg); // 4
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
注釋1:如果
msg.callback
不為空,則直接調用這個Callback的
run()
,這個情況是我們在Message中傳入了一個Callback的方法,它在處理時,會直接調用這個Callback方法。
注釋2、注釋3:如果在建立 Handler(構造函數中)時帶入了 一個 Callback,則直接調用這個 Callback,這個構造函數我們在開篇看過。
注釋4:如果都不是上述情況,則調用
handleMessage()
。
// Handler.java
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
handleMessage()
是一個空方法,英文文檔的解釋為: 子類必須實作這個方法
這就是為什麼我們在自己使用的時候,一定要重寫
handleMessage()
,然後對發送過來的消息做處理。
當這個消息被調用時,也說明了 Message被分發成功了。
到這裡,handler的源碼也講解的差不多了。
3. 在子線程中更新UI
這裡畫一個圖便于了解:
這裡手寫一個簡單例子,在子線程通過Handler去更新UI線程:
public class MainActivity extends AppCompatActivity {
private TextView textView;
private static final int COUNT = 0x00001;
private Handler handler = new Handler() { // 1
@Override
public void handleMessage(Message msg) {
if (msg.what == COUNT) {
textView.setText(String.valueOf(msg.obj));
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.btn);
new Thread() { // 2
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
Thread.sleep(500);
Message msg = new Message();
msg.what = COUNT;
msg.obj = "計數:" + i;
handler.sendMessage(msg); // 3
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
}
注釋1中:我們在主線程建立了一個Handler,那麼其Looper自然就是UI線程的Looper
注釋2:開啟一個子線程
注釋3:在子線程中,持有一個主線程的Handler,然後給這個Handler發送消息。
上面代碼非常容易了解,但是可能會出現記憶體洩漏,是以這裡隻是做一個子線程更新UI的認知。
4. 主線程更新子線程、子線程更新其他子線程
開篇我說過,Handler的出現是子線程不能更新UI,是以Handler起到了這個作用。
這一章是來自于我實習面試的時候,面試官在Handler這點上問我:那主線程怎麼更新子線程?或者子線程怎麼更新其他的子線程?
我當時由于沒有細讀Handler的源碼,是以我粗略的回答:隻要持有對方線程的Handler,就可以更新了。
其實我回答也沒錯,但是這談不上及格的答案,面試官也不會喜歡,下面我将用代碼來示範一遍:
4.1 主線程更新子線程
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private MyThread thread;
class MyThread extends Thread {
private Looper looper;
@Override
public void run() {
Looper.prepare(); // 1
Log.d(TAG, "子線程為->" + Thread.currentThread() + "");
looper = Looper.myLooper(); // 2
Looper.loop(); // 3
}
}
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "主線程為->" + Thread.currentThread() + "");
thread = new MyThread();
thread.start();
try {
sleep(2000); // 4
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler = new Handler(thread.looper) { // 5
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "目前線程為->" + Thread.currentThread() + "");
}
};
mHandler.sendEmptyMessage(0);
}
}
主線程要更新子線程,是以主線程要持有子線程的Handler,是以需要用子線程來構造一個Handlder
注釋1:為子線程建立一個Looper
注釋2:擷取子線程Looper
注釋3:打開子線程Looper的輪詢
注釋4:主線程睡一下,避免 子線程還沒有完全起來的時候就在主線程擷取子線程的Looper了
注釋5:通過子線程的Looper建立屬于子線程的Handler,然後在主線程中使用Handler發送消息。
列印如下:
說明消息已經從主線程傳遞到子線程當中去了。
4.2 子線程更新其他子線程
方法同上。下面寫兩個線程
MyThread1
和
MyThread2
,然後讓2線程給1線程發消息:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private MyThread1 thread1;
private MyThread2 thread2;
private Handler thread1Handler;
class MyThread1 extends Thread {
@Override
public void run() {
Log.d(TAG, "子線程1為->" + Thread.currentThread() + "");
Looper.prepare();
thread1Handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d(TAG, "這個消息是從->" + msg.obj + "線程發過來的,在" + Thread.currentThread() + "線程中擷取的");
}
};
Looper.loop();
}
}
class MyThread2 extends Thread {
@Override
public void run() {
Log.d(TAG, "子線程2為->" + Thread.currentThread() + "");
Message msg = thread1Handler.obtainMessage();
msg.obj = Thread.currentThread();
thread1Handler.sendMessage(msg);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
thread1 = new MyThread1();
thread2 = new MyThread2();
thread1.start();
try {
Thread.sleep(500); // 讓Thread1完全起來
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
列印結果如下:
是以一開始的問題:如果在主線程中更新子線程,在子線程中更新另外的子線程的解答:
(1)要持有目标線程的Handler
(2)因為非UI線程是不會自己開啟輪詢的,是以要手動在子線程中啟動
Looper.prepare()
和
Looper.loop()
another question: HandlerThread
在上面的手動寫法中,我們還要寫
Thread.sleep
保證子線程啟動起來,并且我們還要手動在子線程中手寫
Looper.prepare()
、
Looper.loop()
,這無疑有一些麻煩,Android提供了一個便于在子線程開啟Handler、Looper的類,就是
HandlerThread
5. HandlerThread
HandlerThread顧名思義,就是使用 Handler的線程。它的源碼非常簡單,來看看:
先來看看其構造函數:
// HandlerThread.java
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
}
HandlerThread
是繼承Thread的,在構造函數中,傳入線程名稱和優先級。
接下來看看其 run方法:
// HandlerThread.java
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方法中,寫了
Looper.prepare()
,然後鎖住對象去擷取Looper,通過
Process.setThreadPriority()
來設定線程優先級。
然後調用
onLooperPrepared()
,這是個空方法,表示我們在自己使用HandlerThread時,可以通過重寫這個方法來在Looper準備完成後做一些想做的事情。
如何使用HandlerThread來發送消息呢?HandlerThread實作了下面的方法:
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
通過
getThreadHandler()
我們可以得到目前線程的Handler,并且它會在
getLooper()
中保證Looper是起來的。
是以這也避免了我們手寫 wait()、sleep()的麻煩。
這裡總結一下HandlerThread:
HandlerThread是實作了Thread的線程,它start後會進行 Looper.prepare()、Looper.loop(),并且在我們想要使用其Handler時,能保證Handler不為空。它适合的場景是:主線程需要更新子線程的資料、子線程更新另一個子線程的資料。被更新的子線程可以使用
HandlerThread
。
6. 關于Message的擷取
寫這一章節也是基于面試時比較喜歡問的,Message的擷取有兩種方法:
- 通過 new 方法擷取
- 通過 Message.obtain()
上述第二種方法就是 Handler中的
obtainMessage()
裡調用的方法,兩種方法是有差別的,哪一種更好?
我們來看看
Message.obtain()
:
// Message.java
private static Message sPool;
private static int sPoolSize = 0;
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
可以看到
obtain()
中會去 sPool中擷取一個Message,如果sPool為空,則傳回一個new的Message,否則把sPool指派給m,并将m傳回,然後 sPool取其next。
在結合一下下面的方法:
// Message.java
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
這個方法是在一個消息被使用後,對其進行回收:将所有資料置空,然後将其做為sPool,原來的sPool放到新的後面。
在 Looper.loop()的死循環中,每個循環的最後都會用這個方法。
是以可以輕易的得出結論: 使用
obtainMessage()
要比new一個Message好,但是它要和
recycleUnchecked()
一起使用。
因為
obtainMessage()
維護了一個Message池(Message連結清單),如果池裡沒有就會建立一個Message,如果有就将其取出來。在用完之後,調用
recycleUnchecked()
将使用後的消息資料置空,然後将這個Message放回池裡,下次還可以用它,達到了重複利用的效果,節省了空間。但是如果
recycleUnchecked()
,就不會有Message置空,池子永遠都是空的,是以不配合這個方法一起使用,其實就和new出來的一樣了。
7. 消息屏障
在讀Looper的源碼時,有一個詞一直出現,就是 barrier,意思是屏障。
Handler中除了可以 sendMessage/postDelay消息體之外,還有一個方法: MessageQueue的
postSyncBarrier()
:
// MessageQueue.java
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
... // 插入到隊列的方法同 enqueueMesage()
}
}
它的方法和
enqueueMesage()
差不多,但是我們發現它的傳參,沒有Message,它構造的Message,隻有when,它沒有傳入tartget,也就是說 消息屏障是發送一個target為null的消息,在 MessageQueue的
next()
的死循環中中,一上來就是這麼一個處理:
// MessageQueue
...
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) { // 1
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous()); // 2
}
...
注釋1:判斷目前的消息是否是屏障消息
注釋2:通過 do-while語句拿到MQ中第一個異步的消息并取出來