前言
Android 多線程還有HandleThread,看名字就可以能感覺到得到,會是handler和Thread的綜合使用。那到底是怎麼樣的呢,現在就跟隨Android的源碼來看看他的工作原理是什麼樣的。
我們先看看他的類註解:先看看官方對他的介紹:【 Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.】簡單的翻譯就是這是一個很方便啓動一個擁有looper的的類,這個Looper在這以後是用來創建handler類的,注意:thread的start()方法一定要被調用。
理解HandleThread
看到介紹可能覺得HandleThread有點厲害,有looper還能創建handler。我們抱着這樣的心情去看HandleThread比較難受,所以,我們要換一下思路,這個HandleThread就是個Thread,這個Thread執行工作任務,我們通過handler給這個Thread安排工作。【從使用角度就相當於我們自己起一個工作線程,然後在線程中自己prepareLooper一樣。只不過HandleThread已經把這些做好了,還封裝了優先級設置,安全退出等一些輔助功能,讓我們開發人員使用起來更加方便】
handler我們知道可以用來線程間通信,之前在Handler工作流程梳理裏面分析過【具體在本篇就不細說】,我們這裏要知道,Handler可以把Message發送到MessageQueue中,通過Looper.loop()循環將Message取出並分派到到相應的Handler去處理。同一個線程中只有一個MessageQueue和Looper, Message的處理是串行的。當有Thread使用了Handler,那麼通過Handler給Thread安排任務也是串行執行的,就是一個執行完才執行下一個,所以這個HandleThread不適多耗時任務,這樣任務的執行相互收影響比較嚴重。對於任務量小,使用頻繁的任務來說就比較友好,可以使用一個線程來實現線程池的效果,節省了資源。
源碼分析
看了上面的理解描述後,我們就可以猜到HandleThread代碼邏輯應該不復雜,代碼量也不會很大。事實就是這樣,加上註解,HandleThread的代碼在160多行。
/frameworks/base/core/java/android/os/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;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
...
}
HandleThread繼承自Thread,首先我們就看到了該有的Looper對象,然後從它的構造函數看,可以設置線程優先級和給線程命名。在使用時如果不設置優先級,會默認設置爲Process.THREAD_PRIORITY_DEFAULT;
HandleThread主要的邏輯就在run()方法裏面
1.run()
@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。然後拿到一個looper對象 賦值給mLooper,呃呃呃。這裏爲啥要notifyAll(),在Java中wait()和notify/notifyAll是一起使用的,主要用在多線程中,,用在這裏又是爲什麼?我們在HandleThread搜索一下wait()果然有:
1.2 getLooper()
public Looper getLooper() {
...
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
在getLooper()方法裏,當looper還沒有創建時間,需要當前線程保持wait,這是爲什麼呢?我估計是當線程啓動,在沒有獲取到looper之前,當前線程是空閒的,也沒任務做,就先釋放資源,讓其他線程先執行run()方法來獲取Looper對象,然後將looper賦值給mLooper,【當我看到這裏時是很疑惑的,這個在文章後面來介紹】;
再回到run()方法中;獲mLooper賦值後,隨後調用notifyAll(),【此時其他線程的getLooper()方法也會被喚醒,就可以利用looper去創建handler。然後再handleMessage()中實現處理任務的業務代碼】;然後接下里就主要是進入loop循環了。這就之前分析handler看到的線程最後的狀態是一毛一樣,那就是隨後線程都會進入到loop循環中。
以上就是HandleThread的主要原理了。
----------------------------------------------------------華麗麗的分割線--------------------------------------------------------------------------
疑惑
現在要來說說我在分析那個mlooper被賦值時遇到的疑惑,還記得我們在Handler工作流程梳理事看到Looper.getLooper()最後是從ThreadLoacl中取出的,在Android_ThreadLoacl原理一篇中,我們知道threadLocal保存的變量是與線程相關聯的,所以不同線程get到的Looper是不一樣的。
疑點來了啊,注意聽。假如線程A運行到HandleThread的getLooper()方法,如果此時mlooper爲空。那麼線程A就會wait,阻塞在這裏;那麼後來線程B運行到HandleThread的run()方法,從線程B中獲取到線程B的looper,然後賦值給mLooper;之後線程A就可以被喚醒,返回mLooper;那麼這時候線程A返回的looper其實是在線程B中創建的looper,我就很亂了,,,不應該looper和線程相關的嗎?在這裏怎麼可以這麼玩?
反正我是很亂的,問了同事也還沒問個明白。有一點肯定的,源碼中這麼玩肯定是沒錯的,我需要一個可以讓自己信服的理由。
既然沒問道直接的答案,按我自己試試,然後,我寫了個demo,在主線程中獲取主線程的MainLooper,然後在子線程創建Handler時直接傳入MainLooper。最後發現, 子線程是可以使用主線程的looper。恩恩,終於,用事實說明了,多線是可以公用looper的。好了,HandleThread中的疑惑解決了;
接下來,我就有了新疑惑,我需要繼續搞明白,爲什麼多線程可以共用looper。這和我之前的理解不一樣,我之前的理解是線程中的looper和線程相關,當前線程只能使用當前線程的Looper。
解惑
答案還是得要在源碼裏面找,經過觀察發現HandleThread和我們一般的在子線程中創建Handler的方式是不同的;
- 子線程中直接我們一般是這樣的:
/ *
* <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>
* /
- 使用HandelrThread時是這樣的
private HandlerThread mMyhandleThread = new HandlerThread("MyHandleThread");
Handler mHandler;
mMyhandleThread.start();
mHandler = new Handler(mMyhandleThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
// TODO
}
};
我感覺問題還是在Handler上。那我們就去看看Handler 的構造函數:
/frameworks/base/core/java/android/os/Handler.java
// 1.我們一般手動在子線程就是這樣創建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();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//------------------------華麗麗的分割線------------------------
// 2.我們一般使用HandlerThread時是這樣創建Handler的
public Handler(Looper looper) {
this(looper, null, false);
}
...
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
通過對不同方式創建Handler時調用的構造方法,我們發現 ,handler的如果在構造函數中指定了Looper,就直接使用傳進來的looper對象,如果在構造事沒有傳入looper對象,則此時就需要使用本線程自己的looper;至於messageQueue只是和Looper相關,MessageQueue和handler並沒有直接聯繫。
結論
所以我們得出結論,多線程可以使用同一個Looper,相應的也就是使用了相同的MessageQueue。