聲明:原創文章,轉載請註明出處https://www.jianshu.com/p/48976893bd11
我們知道在App中一般多會有多個線程,多線程之間難免需要進行通信。在我們平時開發中線程通信用的最多的就是Handler,例如子線程進行數據處理,在主線程中進行UI更新。當然了除了Handler這種通信方式外,線程間的通信還有其他幾種方式:管道Pip、共享內存、通過文件及數據庫等。這裏由於篇幅有限,我們主要來看下Handler以及其實現原理。
相信做過Android的朋友對Handler一定不陌生,我們先來回顧下Handler的用法:
public class MainActivity extends AppCompatActivity {
private Handler mHanlde;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHanlde = new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof String){
Log.d("主線程接受到的消息:",(String)msg.obj);
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.obj = "這是從子線程發送的消息";
mHanlde.sendMessage(message);
}
}).start();
}
}
可以看到我們在主線程中建了一個Handler,又創建了一個子線程,並在子線程中利用主線程創建的Handler發送了一條消息。運行起來看下打印日誌:
06-12 16:08:31.618 25807-25807/simplae.handledemo D/主線程接受到的消息:: 這是從子線程發送的消息
可以看到子線程中的消息被順利發送到了主線程。
接下來我們來嘗試下將主線程中的消息發送到子線程中去,注意Handler都需要創建在接受消息的線程,這一點在下面源碼分析的地方會提到:
public class MainActivity extends AppCompatActivity {
private Handler mHanlde;
private Handler subThreadHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
subThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof String) {
Log.d("子線程接受到的消息:",(String)msg.obj);
}
}
};
}
}).start();
SystemClock.sleep(1000);
Message message = new Message();
message.obj = "這是從主線程發送的消息";
subThreadHandler.sendMessage(message);
}
}
這裏我在發送消息前延遲了1秒,主要是防止發送消息時這個子線程還沒初始化好,導致拿到的handler爲空,這裏是爲了演示方面,實際開發中切記不要在主線程中加這麼多騷操作。運行下看下:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:203)
at android.os.Handler.<init>(Handler.java:117)
at simplae.handledemo.MainActivity$1$1.<init>(MainActivity.java:22)
at simplae.handledemo.MainActivity$1.run(MainActivity.java:22)
at java.lang.Thread.run(Thread.java:764)
我們發現程序拋異常了,說是在線程中如果不調用Looper.prepare()就無法創建Handler。
那麼Looper對象是什麼,是幹嘛的呢?一般源碼中在類的開頭都會有對類的功能作用做一個簡單的說明,我們打開Looper這個類,可以看到開頭有這樣一段註釋:
/**
* Class used to run a message loop for a thread. Threads by default do
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
*
* <p>Most interaction with a message loop is through the
* {@link Handler} class.
*
* <p>This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper.
*
* <pre>
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }</pre>
*/
可以看到這裏第一句就對Looper的作用做了說明:
Class used to run a message loop for a thread.
即爲一個線程做消息循環的一個類,說白了Looper就是一個調度器,可以簡單理解爲負責消息的存取。然後第二句也很重要:
Threads by default do not have a message loop associated with them;
to create one, call prepare in the thread that is to run the loop,
and then loop to have it process messages until the loop is stopped.
意思是線程默認是沒有Looper的,可以在線程中調用prepare方法來創建,之後可以通過它來循環處理消息直到Looper退出,稍後會對其進行詳細的分析。這樣我們在創建Handler之前先添加這句話試下:
...
Looper.prepare();
subThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof String) {
Log.d("子線程接受到的消息:",(String)msg.obj);
}
}
};
...
運行後,果然不報錯了,不過子線程並未接受到主線程發送的消息。這裏如果我們在子線程中創建Handler,創建完之後還需調用Looper.loop()方法,完整代碼如下:
public class MainActivity extends AppCompatActivity {
private Handler mHanlde;
private Handler subThreadHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
subThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof String) {
Log.d("子線程接受到的消息:",(String)msg.obj);
}
}
};
Looper.loop();
}
}).start();
SystemClock.sleep(1000);
Message message = new Message();
message.obj = "這是從主線程發送的消息";
subThreadHandler.sendMessage(message);
}
}
這樣我們就成功接受到消息了。可以看到我們在子線程中創建Handler問題還是很多的,相比主線程,子線程在創建Handler前後分別得調用Looper的prepare和loop方法,這到底是怎麼回事?不要着急接下來我們就一起來看下它背後究竟隱藏着怎樣不爲人知的祕密。
注意分析源碼的時候一定要按程序執行流程往下看但是也不能太扣細節,要從整體上來把控,而且一定要自己親自去看代碼,如果只是去網上看別人的源碼分析文章,自己沒有實踐,那是很難看懂的。即使看懂了,過幾天就沒印象了。所以接下來我分析源碼的時候,還是建議你自己在編輯器中跟着一起查看源代碼,這裏只是起到一個引導的作用。
好了言歸正傳,我們就從第一句Looper.prepare()開始吧
public static void prepare() {
prepare(true);
}
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));
}
點開Looper.prepare()方法,我們發現裏面就只有一句代碼,調用了prepare(true)的重載方法,再點進去我們發現代碼很簡單主要是給ThreadLocal設置了一個Looper對象,這裏出現了一個新的對象就是ThreadLocal,這個對象對於Handler可以說是非常重要了。所以要想接下去分析Handler還是先把這個理解下。
ThreadLocal說白了就是用來存放數據的,不過它有個與衆不同的特點,就是它的存取是和當前線程關聯的,換句話說,用ThreadLocal存放的數據是放在調用ThreadLocal的線程中的,其他線程是訪問不到的。這樣說可能還是有點抽象,我們不妨看下他的源碼,你會豁然開朗,我們點開它的set方法:
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 對象,我們數據就存在這個對象中。那麼我們看下這個getMap方法是怎麼獲取這個 ThreadLocalMap對象:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
額。。有沒有一種被耍了的感覺,原來這個對象就是直接從當前線程中來的。到這裏你大概就明白了,原來通過ThreadLocal的set方法保存的數據就是直接存放在當前線程中的threadLocals變量中,這個變量是一個ThreadLocalMap 對象,而ThreadLocalMap 對象具體是怎麼保存數據的,可以點開map.set(this, value)
方法看下:
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();
}
可以看到數據其實是以數組的形式保存的,具體的存儲細節大家可以好好看看代碼,這裏就不過多講解了。
知道了ThreadLocal如何存數據後,我們再來看下如何取數據,讀取數據只用調用ThreadLocal.get()
方法:
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();
}
很簡單,就是獲取當前的線程的ThreadLocalMap對象,然後獲取ThreadLocalMap中保存的數據。
到這裏,我們可以對ThreadLocal下個結論了:通過ThreadLocal保存的數據,其實就是保存在當前線程中,其他線程是無法訪問到的。當你在某個線程中調用ThreadLocal的get方法,獲取的數據也就是該線程中的數據。
好了,對ThreadLocal有了一個大致的瞭解,我們再回過頭來看下Looper.prepare()的源碼:
public static void prepare() {
prepare(true);
}
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));
}
可以看到通過ThreadLocal的set方法,在當前線程保存了一個Looper對象,我們進入Looper的構造方法看下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper的構造方法中實例化了一個MessageQueue對象,這個MessageQueue對象其實就是一個隊列,你可以看成是一根水管,我們的消息就像裏面的水,只能從一端流入,從另一端流出,在水管裏的消息都是等待處理的,而消息的流入和流出都是由Looper這個對象控制的。
這樣Looper.prepare()我們簡單歸納下,主要做如下幾件事情:
- 1.在當前線程保存了一個Looper對象
- 2.在Looper對象中創建了一個消息隊列
好了,對Looper.prepare()瞭解了後,我們再來看下下面的代碼:
subThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof String) {
Log.d("子線程接受到的消息:",(String)msg.obj);
}
}
};
主要就是對Handler對象的實例化,我們進去看下:
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;
}
可以看到,通過Looper.myLooper();
方法獲取一個Looper對象,我們看下這個方法內部:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
。。。就是通過ThreadLocal的get方法,來獲取當前線程保存的Looper對象,也就是要獲取前面通過Looper的Looper.prepare()方法保存的Looper對象。接下來就是判空,如果獲取的Looper對象爲空則直接拋出一個異常:
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
是不是很熟悉,就是我們文章開頭在子線程中沒有調用Looper.myLooper()就是直接創建Handler拋出的異常。
接下來通過 mQueue = mLooper.mQueue;
來獲取Looper中的消息隊列,至於其他代碼我們暫且放下,因爲先理清主要流程,不必太拘泥於細節。
好了,創建完Handler實例,接下來我們來看下接下來的Looper.loop();
:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (slowDispatchThresholdMs > 0) {
final long time = end - start;
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, "Dispatch took " + time + "ms on "
+ Thread.currentThread().getName() + ", h=" +
msg.target + " cb=" + msg.callback + " msg=" + msg.what);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
在這個方法裏可以看到,主要做了這麼這麼幾件事,先通過myLooper()方法獲取當前線程保存的Looper對象,然後獲取該Looper對象中的消息隊列MessageQueue ,之後會調用該消息隊列的next()方法,調用這個方法線程就進入阻塞狀態,一直判斷這個消息隊列中有無消息,有消息就取出來,並且執行如下語句來處理該消息,如果沒有消息,則一直處於阻塞狀態
msg.target.dispatchMessage(msg);
這裏有個疑問msg的target是什麼,調用完 Looper.loop()之後,我們知道線程現在一直處於阻塞狀態,如果消息隊列中有消息就取出來處理。那麼接下來就是等着消息進入到這個隊列。很顯然這肯定和消息的發送有關,我們趕緊來看下,消息是如何發送的,這個消息到底跑哪去了,我們點開Handler的sendMessage方法,結果發現最終都會執行這個方法:
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);
}
注意這裏的mQueue就是Handler實例化時從當前線程獲取到的Looper中的消息隊列,最終會調用enqueueMessage方法來使發送的消息進入這個消息隊列。我們再進入到這個方法看下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
第一句就解決了我們上面的疑惑,原來這個msg的target就是Handler本身,再接下來就是調用這個隊列的
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) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
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) {
nativeWake(mPtr);
}
}
return true;
}
這裏其實就是隊列的算法實現,這裏由於篇幅有限就不深入分析具體的細節了。
這樣消息隊列中就有消息了,我們之前一直阻塞的線程就可以獲取到消息來處理了,其處理的代碼就是這句;
msg.target.dispatchMessage(msg);
剛纔也看到了這個target就是Handler本身,我們趕緊來看下這個Handler的dispatchMessage方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到裏面做了一些非空判斷,當msg.callback和mCallback 都爲空的時候會執行handleMessage方法,這個方法進入看下:
public void handleMessage(Message msg) {
}
我們發現是空的,如果要處理需要Handler的子類實現,也就是我們上面新建Handler時覆寫的handleMessage方法。可能有的朋友會疑惑這裏的msg.callback和mCallback 什麼時候不爲空呢?
首先我們來看下msg.callback這種情況,說這種情況之前我們先來回顧下,還有哪些方式我們可以在子線程中更新UI,這裏我們一般用的比較多的就是如下幾個:
- 1、調用主線程中Handler的post或postDelayed等方法:
mMainHandler.post(new Runnable() {
@Override
public void run() {
}
});
- 2、直接調用Activity的runOnUiThread方法:
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
我們先來看第一種,我們進入Handler的post方法:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
原來還是發送一條消息,這裏是通過getPostMessage方法把Runnable 封裝成一條消息,我們進入看下:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
相信已經不用做過多解釋了,這個Message的callback就是一個Runnable 對象,這裏Message的callback也就被賦值了,回到上面當msg.callback不爲空的時候,會調用handleCallback(msg);方法,我們進入看下:
private static void handleCallback(Message message) {
message.callback.run();
}
非常簡單,就是調用通過post方法傳進去的Runnable 對象的run方法。接下來我們看下runOnUiThread內部代碼:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
可以看到也是很簡單,如果當前線程不是UI線程,就調用Handler的post方法把Runnable發送到UI線程,如果是主線程就直接調用Runnable 的run方法。
好了到這裏我們簡單分析了Message的callback不爲空的情況,接下來我們看下Handler的mCallback什麼時候不爲空,這個Callback是一個接口:
public interface Callback {
public boolean handleMessage(Message msg);
}
裏面定義了處理消息的方法,它在什麼時候被賦值的呢,Handler除了空參數的構造方法外還有其他幾個構造方法,這些構造方法允許你直接傳入這個callback的實現,也就是對消息的處理方法,這樣就不用重新再像文章開頭那樣覆寫Handler的handleMessage方法。比如:
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
這裏我們創建Hanlder時就傳入了一個CallBack,直接在這裏就可以處理消息。
好了到這裏我們就隨着流程,分析了下消息傳遞的原理,可能大家對流程還不是非常清晰,不過沒關係,我把有關的對象畫成一個圖:
上面的圖可以很直觀的反應消息傳遞時有關對象之間的關係,可以看到,在需要接受消息的線程A中創建了一個Handler,這個Handler對象中有一個Looper對象,而這個Looper對象中有個消息隊列,裏面存了待處理的消息。然後在需要發消息的線程B中調用線程A中HanlderA的sendMessage方法,這樣線程B中的消息就已經被傳入線程A了。這裏需要注意的是,每個線程只關聯一個單例的Looper對象,這個Looper對象是通過Looper.prepare創建,如果沒有調用這句就直接創建Handler就會初始化失敗而報錯,這樣就實現了線程的通信,怎麼樣是不是很巧妙!
可能有些朋友還有疑惑,爲什麼在子線程創建Handler前後還要分別調用Looper.prepare()和Looper.loop(),而在主線程中卻不用調用,其實並不是主線程不用調用,而且在主線程一啓動就已經調用了,我們來看下主線程中的代碼:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
有兩句代碼很明顯:
...
Looper.prepareMainLooper();
...
Looper.loop();
...
可以看到在主線程開始的時候,Looper就已經初始化好了,並且從Looper.loop()也可以看到,主線程在阻塞狀態,等待處理消息。如果需要在主線程接受消息就只需要創建一個Handler就可以了。
到這裏有關Handler的機制就分析的差不多了,如果看得還不是很懂,還是建議你自己在編輯器中閱讀源代碼,因爲只有自己親自看了,敲了纔會印象深刻。