咱們在Android開發過程當中,幾乎都離不開線程。可是你對線程的瞭解有多少呢?它完美運行的背後,究竟隱藏了多少鮮爲人知的祕密呢?線程間互通暗語,傳遞信息到底是如何作到的呢?Looper、Handler、MessageQueue究竟在這背後進行了怎樣的運做。本期,讓咱們一塊兒從Thread開始,逐步探尋這個完美的線程鏈背後的祕密。
注意,大部分分析在代碼中,因此請仔細關注代碼哦!php
在這一個環節,咱們將一塊兒一步步的分析Thread的建立流程。
話很少說,直接代碼裏看。java
// 建立Thread的公有構造函數,都調用的都是這個私有的init()方法。咱們看看到底幹什麼了。
/** * * @param 線程組 * @param 就是咱們平時接觸最多的Runnable同窗 * @param 指定線程的名稱 * @param 指定線程堆棧的大小 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
// 獲取當前正在運行的線程
// 當前正在運行的線程就是該咱們要建立的線程的父線程
// 咱們要建立的線程會從父線程那繼承一些參數過來
// 注意哦,此時仍然是在原來的線程,新線程此時尚未建立的哦!
Thread parent = currentThread();
if (g == null) {
g = parent.getThreadGroup(); //若是沒有指定ThreadGroup,將獲取父線程的TreadGroup
}
g.addUnstarted(); //將ThreadGroup中的就緒線程計數器增長一。注意,此時線程還並無被真正加入到ThreadGroup中。
this.group = g; //將Thread實例的group賦值。從這裏開始線程就擁有ThreadGroup了。
this.target = target; //給Thread實例設置Runnable。之後start()的時候執行的就是它了。
this.priority = parent.getPriority(); //設置線程的優先權重爲父線程的權重
this.daemon = parent.isDaemon(); //根據父線程是不是守護線程來肯定Thread實例是不是守護線程。
setName(name); //設置線程的名稱
init2(parent); //納尼?又一個初始化,參數仍是父線程。不急,稍後在看。
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize; //設置線程的堆棧大小
tid = nextThreadID(); //線程的id。這是個靜態變量,調用這個方法會自增,而後做爲線程的id。
}
複製代碼
在Thread的init()方法中,比較重要的是會經過一個currentThread()
這樣的native函數經過底層從虛擬機中獲取到當前運行的線程。android
因此在Thread初始化的時候,仍然是在建立它的線程中。不難猜想出,其實Java層的Thread只是對底層的封裝而已。c++
private void init2(Thread parent) {
this.contextClassLoader = parent.getContextClassLoader(); //設置ClassLoader成員變量
this.inheritedAccessControlContext = AccessController.getContext(); //設置訪問權限控制環境
if (parent.inheritableThreadLocals != null) {
this.inheritableThreadLocals = ThreadLocal.createInheritedMap( //建立Thread實例的ThreadLoacaleMap。須要用到父線程的ThreadLocaleMap,目的是爲了將父線程中的變量副本拷貝一份到當前線程中。
//ThreadLocaleMap是一個Entry型的數組,Thread實例會將變量副本保存在這裏面。
parent.inheritableThreadLocals);
}
}
複製代碼
至此,咱們的Thread就初始化完成了,Thread的幾個重要成員變量都賦值了。數組
一般,咱們這樣了啓動一條線程。緩存
Thread threadDemo = new Thread(() -> {
});
threadDemo.start();
複製代碼
那麼start()背後究竟隱藏着什麼樣不可告人的祕密呢?是人性的扭曲?仍是道德的淪喪?讓咱們一塊兒點進start()。探尋start()背後的祕密。安全
//如咱們所見,這個方法是加了鎖的。緣由是避免開發者在其它線程調用同一個Thread實例的這個方法,從而儘可能避免拋出異常。
//這個方法之因此可以執行咱們傳入的Runnable裏的run()方法,是應爲JVM調用了Thread實例的run()方法。
public synchronized void start() {
//檢查線程狀態是否爲0,爲0表示是一個新狀態,即還沒被start()過。不爲0就拋出異常。
//就是說,咱們一個Thread實例,咱們只能調用一次start()方法。
if (threadStatus != 0)
throw new IllegalThreadStateException();
//從這裏開始才真正的線程加入到ThreadGroup組裏。再重複一次,前面只是把nUnstartedThreads這個計數器進行了增量,並無添加線程。
//同時,當線程啓動了以後,nUnstartedThreads計數器會-1。由於就緒狀態的線程少了一條啊!
group.add(this);
started = false;
try {
//又是個Native方法。這裏交由JVM處理,會調用Thread實例的run()方法。
nativeCreate(this, stackSize, daemon);
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this); //若是沒有被啓動成功,Thread將會被移除ThreadGroup,同時,nUnstartedThreads計數器又增量1了。
}
} catch (Throwable ignore) {
}
}
}
複製代碼
好把,最精華的函數是nativeCreate(this, stackSize, daemon)
,會去調用底層的JNI函數Thread_nativeCreate()
,進一步的會調用底層的Thread類的Thread::CreateNativeThread()
函數。數據結構
Thread::CreateNativeThread()
函數在/art/runtime/thread.cc
文件中(注:CoorChice用的是6.0.0-r1的源碼)。它會在去建立一個c/c++層的Thread對象,而且會關聯Java層的Thread對象(其實就是保存一個Java層Thread對象的引用而已)。接着,會經過c/c++層的pthread_create()
函數去建立並啓動一個新線程。這條代碼必需要看看了:多線程
pthread_create_result = pthread_create(&new_pthread, &attr,
Thread::CreateCallback, child_thread);
複製代碼
這裏咱們須要注意第三個參數位置的Thread::CreateCallback
,它會返回一個Java層Thread類的run()方法指針,在Linux層的pthread線程建立成功後,將會調用這個run()方法。這就是爲何咱們調用start()方法後,run()方法會被調用的緣由。併發
從上面的分析咱們能夠知道,其實Java的線程Thread仍是用的Linux那一套 pthread
的東西,而且一條線程真正建立並運行在虛擬機中時,是在調用start()方法以後。因此,若是你建立了一條線程,可是從沒調用過它的start()方法,就不會有條新線程生成,此時的Thread對象和主線程裏的一個普通對象沒什麼區別。若是你企圖調用 run()
方法去試圖啓動你的線程,那真是大錯特錯了!這樣不過至關於在主線程中調用了一個Java方法而已。
因此,Java中的線程在Android中實際上走的仍是Linux的pthread那一套。
//沒錯,就是這麼簡單!僅僅調用了Runnable類型的成員變量target的run()方法。至此,咱們須要執行的代碼就執行起來了。
//至於這個@Overrid的存在,徹底是由於Thread自己也是一個Runnable!就是說,咱們的Thread也能夠做爲一個Runnable來使用。
@Override
public void run() {
if (target != null) {
target.run();
}
}
複製代碼
看,若是不調用start()方法,你能夠把Thread看成一個Handler去使用!!
public void test_1() {
Thread thread1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
}, "Thread_1");
Thread thread2 = new Thread(thread1, "Thread_2");
thread2.start();
}
---
輸出:
Thread_2
複製代碼
咱們平時使用Thread.sleep()的頻率也比較高,因此咱們在一塊兒研究研究Thread.sleep()被調用的時候發生了什麼。
在開始以前,先介紹一個概念——納秒。1納秒=十億分之一秒。可見用它計時將會很是的精準。可是因爲設備限制,這個值有時候並非那麼準確,但仍是比毫秒的控制粒度小不少。
//平時咱們調用的Thread.sleep(long)最後調用到這個方法來,後一個陌生一點的參數就是納秒。
//你能夠在納秒級控制線程。
public static void sleep(long millis, int nanos) throws InterruptedException {
//下面三個檢測毫秒和納秒的設置是否合法。
if (millis < 0) {
throw new IllegalArgumentException("millis < 0: " + millis);
}
if (nanos < 0) {
throw new IllegalArgumentException("nanos < 0: " + nanos);
}
if (nanos > 999999) {
throw new IllegalArgumentException("nanos > 999999: " + nanos);
}
if (millis == 0 && nanos == 0) {
if (Thread.interrupted()) { //當睡眠時間爲0時,檢測線程是否中斷,並清除線程的中斷狀態標記。這是個Native的方法。
throw new InterruptedException(); //若是線程被設置了中斷狀態爲true了(調用Thread.interrupt())。那麼他將拋出異常。若是在catch住這個異常以後return線程,那麼線程就中止了。
//須要注意,在調用了Thread.sleep()以後,再調用isInterrupted()獲得的結果永遠是False。別忘了Thread.interrupted()在檢測的同時還會清除標記位置哦!
}
return;
}
long start = System.nanoTime(); //相似System.currentTimeMillis()。可是獲取的是納秒,可能不許。
long duration = (millis * NANOS_PER_MILLI) + nanos;
Object lock = currentThread().lock; //得到當前線程的鎖。
synchronized (lock) { //對當前線程的鎖對象進行同步操做
while (true) {
sleep(lock, millis, nanos); //這裏又是一個Native的方法,而且也會拋出InterruptedException異常。
//據我估計,調用這個函數睡眠的時長是不肯定的。
long now = System.nanoTime();
long elapsed = now - start; //計算線程睡了多久了
if (elapsed >= duration) { //若是當前睡眠時長,已經知足咱們的需求,就退出循環,睡眠結束。
break;
}
duration -= elapsed; //減去已經睡眠的時間,從新計算須要睡眠的時長。
start = now;
millis = duration / NANOS_PER_MILLI; //從新計算毫秒部分
nanos = (int) (duration % NANOS_PER_MILLI); //從新計算微秒部分
}
}
}
複製代碼
經過上面的分析能夠知道,使線程休眠的核心方法就是一個Native函數sleep(lock, millis, nanos)。這個sleep()
對應底層的一個JNI函數,這個JNI函數最終會調用到c/c++中對應的Thread的條件變量的 TimedWait()
函數。這個條件變量是應該是Android中本身定義的條件變量,固然,這裏的TimedWait()
函數天然也是Android本身實現的。在這個函數裏,Android直接使用了Linux的futex()
函數。這個futex()
函數會調用syscall()
函數,經過一種名爲【快速用戶區互斥鎖】的鎖去執行鎖定的。futex()
的效率比phtread_cond_wait()
要高不少。
Android爲了確保休眠的準確性,在這裏還使用了一個while()
循環,在每次線程從底層被喚醒後,檢查一下是否休眠夠了足夠的時長。若是不夠就讓它繼續休眠。
同時,須要注意一點,若是線程的interruted狀態在調用sleep()方法時被設置爲true,那麼在開始休眠循環前會拋出InterruptedException異常。
這個方法是Native的。調用這個方法能夠提示cpu,當前線程將放棄目前cpu的使用權,和其它線程從新一塊兒爭奪新的cpu使用權限。當前線程可能再次得到執行,也可能沒得到。就醬。
你們必定常常見到,不管是哪個對象的實例,都會在最下面出現幾個名爲wait()的方法。等待?它們到底是怎樣的一種存在,讓咱們一塊兒點擊去看看。
哎喲我去,都是Native函數啊。
咱們可能過去都寫過形如這樣的代碼:
new Thread(()->{
...
Looper.prepare();
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Looper.loop();
}).start()
複製代碼
不少同窗知道,在線程中使用Handler時(除了Android主線程)必須把它放在Looper.prepare()和Looper.loop()之間。不然會拋出RuntimeException異常。可是爲何要這麼作呢?下面咱們一塊兒來扒一扒這其中的內幕。
當Looper.prepare()被調用時,發生了什麼?
public static void prepare() {
prepare(true); //最終其實執行的是私有方法prepare(boolean quitAllowed)中的邏輯
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) { //先嚐試獲取是否已經存在一個Looper在當前線程中,若是有就拋個異常。
//這就是爲何咱們不能在一個Thread中調用兩次Looper.prepare()的緣由。
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed)); //首次調用的話,就建立一個新的Looper。
}
//Looper的私有構造函數
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); //建立新的MessageQueue,稍後在來扒它。
mThread = Thread.currentThread(); //把當前的線程賦值給mThread。
}
複製代碼
通過上面的分析,咱們已經知道Looper.prepare()調用以後發生了什麼。
可是問題來了!sThreadLocal是個靜態的ThreadLocal<Looper> 實例(在Android中ThreadLocal的範型固定爲Looper)。那麼,Looper.prepare()既然是個靜態方法,Looper是如何肯定如今應該和哪個線程創建綁定關係的呢?咱們接着往裏扒。
來看看ThreadLocal的get()、set()方法。
public void set(T value) {
Thread t = Thread.currentThread(); //一樣先獲取到當前的線程
ThreadLocalMap map = getMap(t); //獲取線程的ThreadLocalMap
if (map != null)
map.set(this, value); //儲存鍵值對
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread(); //重點啊!獲取到了當前運行的線程。
ThreadLocalMap map = getMap(t); //取出當前線程的ThreadLocalMap。這個東西是個重點,前面已經提到過。忘了的同窗在前面再看看。
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
//能夠看出,每條線程的ThreadLocalMap中都有一個<ThreadLocal,Looper>鍵值對。綁定關係就是經過這個鍵值對創建的。
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
複製代碼
ThreadLocal是Looper類中的靜態常量,因此它對全部線程來講都是可見的。從上面代碼也能夠看出,調用ThreadLocal的set/get方法,實際操做的是Thread的ThreadLocalMap,也就是說每一個Thread的ThreadLocalMap是Thread私有的。這樣的設計,使得即便在併發的狀況下,每一個線程都invoke ThreadLocal的get/set方法,可是因爲每一個線程實際操做的都是本身的ThreadLocalMap,互不影響,因此是線程安全的。關於線程的內存你能夠在CoorChice的這兩篇文章中找到點線索:《【拒絕一問就懵】之有必要單獨講講線程:https://juejin.im/post/5cdc164af265da0353790245》,《【拒絕一問就懵】之你多少要懂點內存回收機制:https://juejin.im/post/5cdc0faf51882568666dfe2f》。
這樣設計主要仍是爲了將邏輯分離出去,由於實現方案可能會改變。若是之後修改了ThreadLocalMap的管理邏輯,只要接口功能沒變,Looper和Thread就不會受到影響。而這套邏輯也能夠直接拿來給其它方案使用。便於修改,便於複用。
Handler能夠用來實現線程間的通行。在Android中咱們在子線程做完數據處理工做時,就經常須要經過Handler來通知主線程更新UI。平時咱們都使用new Handler()
來在一個線程中建立Handler實例,可是它是如何知道本身應該處理那個線程的任務呢。下面就一塊兒扒一扒Handler。
public Handler() {
this(null, false);
}
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(); //重點啊!在這裏Handler和當前Thread的Looper綁定了。Looper.myLooper()就是從ThreadLocale中取出當前線程的Looper。
if (mLooper == null) {
//若是子線程中new Handler()以前沒有調用Looper.prepare(),那麼當前線程的Looper就還沒建立。就會拋出這個異常。
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //賦值Looper的MessageQueue給Handler。
mCallback = callback;
mAsynchronous = async;
}
複製代碼
咱們都知道,在Handler建立以後,還須要調用一下Looper.loop(),否則發送消息到Handler沒有用!接下來,扒一扒Looper究竟有什麼樣的魔力,可以把消息準確的送到Handler中處理。
public static void loop() {
final Looper me = myLooper(); //這個方法前面已經提到過了,就是獲取到當前線程中的Looper對象。
if (me == null) {
//沒有Looper.prepare()是要報錯的!
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //獲取到Looper的MessageQueue成員變量,這是在Looper建立的時候new的。
//這是個Native方法,做用就是檢測一下當前線程是否屬於當前進程。而且會持續跟蹤其真實的身份。
//在IPC機制中,這個方法用來清除IPCThreadState的pid和uid信息。而且返回一個身份,便於使用restoreCallingIdentity()來恢復。
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) { //重點(敲黑板)!這裏是個死循環,一直等待抽取消息、發送消息。
Message msg = queue.next(); // 從MessageQueue中抽取一條消息。至於怎麼取的,咱們稍後再看。
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 traceTag = me.mTraceTag; //取得MessageQueue的跟蹤標記
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); //開始跟蹤本線程的MessageQueue中的當前消息,是Native的方法。
}
try {
msg.target.dispatchMessage(msg); //嘗試分派消息到和Message綁定的Handler中
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag); //這個和Trace.traceBegin()配套使用。
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
final long newIdent = Binder.clearCallingIdentity(); //what?又調用這個Native方法了。這裏主要是爲了再次驗證,線程所在的進程是否發生改變。
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(); //回收釋放消息。
}
}
複製代碼
從上面的分析能夠知道,當調用了Looper.loop()以後,線程就就會被一個for(;;)死循環阻塞,每次等待MessageQueue的next()方法取出一條Message纔開始往下繼續執行。而後經過Message獲取到相應的Handler (就是target成員變量),Handler再經過dispatchMessage()方法,把Message派發到handleMessage()中處理。
這裏須要注意,當線程loop起來是時,線程就一直在循環中。就是說Looper.loop()後面的代碼就不能被執行了。想要執行,須要先退出loop。
Looper myLooper = Looper.myLoop();
myLooper.quit(); //普通退出方式。
myLooper.quitSafely(); //安全的退出方式。
複製代碼
如今又產生一個疑問,MessageQueue的next()方法是如何阻塞住線程的呢?接下來,扒一扒這個幕後黑手MessageQueue。
MessageQueue是一個用單鏈的數據結構來維護消息列表。
Message next() {
//檢查loop是否已經爲退出狀態。mPrt是Native層的MessageQueue的地址。經過這個地址能夠和Native層的MessageQueue互動。
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0; //時間標記,當且僅當第一次獲取消息時才爲0。由於它在死循環外面啊!
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
//若是不是第一次獲取消息,調用Native的函數,讓虛擬機刷新全部的餓Binder命令,確保進程在執行可能阻塞的任務以前,釋放以前的對象。
}
//這是一個Native的方法。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) { //鎖住MessageQueue
//獲取當前的系統時間,用於後面和msg.when進行比較。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; //得到當前MessageQueue中的第一條消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) { //這個判斷的意義在於只有到了Message應該被髮送的時刻纔去發送,不然繼續循環。
//計算下一條消息的時間。注意最大就是Integer.MAX_VALUE。
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; //返回一條消息給Looper。
}
} else {
// 若是取到的Message爲null,將時間標記設置爲-1。
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
複製代碼
能夠看到。MessageQueue在取消息(調用next())時,會進入一個死循環,直到取出一條Message返回。這就是爲何Looper.loop()會在queue.next()處等待的緣由。
在這個方法中須要注意一個參數mPtr
,它是底層的MessageQueue對象的地址。就是說Android的c/c++層也有一套與Java層對應的Handler機制,而咱們的MessageQueue因爲持有了一個底層的引用,天然就成了Java層的Handler機制和底層的溝通橋樑了。
上面方法中出現了一個nativePollOnce(ptr, nextPollTimeoutMillis);
函數的調用。線程會被阻塞在這個地方。這個native方法會調用到底層的JNI函數android_os_MessageQueue_nativePollOnce()
,進一步調用c/c++層的nativeMessageQueue
的pollOnce()
函數,在這個函數中又會經過本線程在底層的Looper的pollOnce()
函數,進而調用pollInner()
函數。在pollInner()
函數中會調用epoll_wait()
函數,這個函數會將線程阻塞在這,直到被超時或者檢測到pipe中有事件發生。那麼阻塞在這怎麼喚醒呢,咱們下面在說。
那麼,一條Message是如何添加到MessageQueue中呢?要弄明白最後的真相,咱們須要調查一下mHandler.post()這個方法。
Handler的post()系列方法,最終調用的都是下面這個方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //在這裏給Message的target賦值。
if (mAsynchronous) {
msg.setAsynchronous(true); //若是是異步,就標記爲異步
}
return queue.enqueueMessage(msg, uptimeMillis); //就是這個方法把Message添加到線程的MessageQueue中的。
}
複製代碼
接下來就看看MessageQueue的enqueueMessage()做了什麼。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) { //沒Handler調用是會拋異常的啊
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) { //不能使用一條正在使用中的Message。
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) { //鎖住MessageQueue再往裏添加消息。
if (mQuitting) { //若是MessageQueue被標記爲退出,就返回。
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(); //切換Message的使用狀態爲未使用。
msg.when = when; //咱們設置的延遲發送的時間。
//通過下面的邏輯,Message將會被「儲存」在MessageQueue中。實際上,Message在MessageQueue中的儲存方式,
//是使用Message.next逐個向後指向的單鏈表結構來儲存的。好比:A.next = B, B.next = C...
Message p = mMessages; //嘗試獲取當前Message
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 若是爲null,說明是第一條。
msg.next = p;
mMessages = msg; //設置當前的Message爲傳入的Message,也就是做爲第一條。
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//不知足做爲第一條Message的條件時,經過下面的逐步變換,將它放在最後面。這樣便把Message「儲存」到MessageQueue中了。
for (;;) {
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;
}
複製代碼
上一節面CoorChice說過,MessageQueue在next()
方法中會阻塞在nativePollOnce()
這個地方,其實是阻塞在了底層的Looper的epoll_wait()
這個地方等待喚醒呢。看到上面這段代碼的最後面沒?nativeWake()
,赤裸裸的代表就是喚醒。實際上這個nativeWake()
函數代表pipe寫端有write事件發生,從而讓epoll_wait()
退出等待。
至此,咱們已經揭露了Looper、Handler、MessageQueue隱藏的祕密。
也許你已經注意到在主線程中能夠直接使用Handler,而不須要Looper.prepare()和Looper.loop()。爲何能夠作到這樣呢?根據以前的分析能夠知道,主線程中必然存在Looper.prepare()和Looper.loop()。既然如此,爲何主線程沒有被loop()阻塞呢?看一下ActivityThread來弄清楚究竟是怎麼回事。
//這個main()方法能夠認爲是Android應用的起點
public static void main(String[] args) {
。
。
。
Looper.prepareMainLooper(); //主要做用和咱們平時調用的Looper.prepare()差很少
ActivityThread thread = new ActivityThread(); //建立本類實例
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler(); //重點啊!這裏取得了處理主線程事物的Handler。
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop(); //開始循環。能夠看到,主線程本質上是阻塞的!
。
。
。
}
複製代碼
注意ActivityThread並無繼承Thread,它的Handler是繼承Handler的私有內部類H.class。在H.class的handleMessage()中,它接受並執行主線程中的各類生命週期狀態消息。UI的16ms的繪製也是經過Handler來實現的。也就是說,主線程中的全部操做都是在Looper.prepareMainLooper()和Looper.loop()之間進行的。進一步說是在主Handler中進行的。
通過上面的揭露,咱們已經對線程及其相互之間通信的祕密有所瞭解。掌握了這些之後,相信在之後的開發過程當中咱們能夠思路清晰的進行線程的使用,而且可以吸取Android在設計過程當中的精華思想。
感受不錯就關注我,都不幹就點個贊!😘