Android 異步任務(wù)知識(shí)梳理(2) - HandlerThread 源碼解析

一、概述

剛開始接觸HandlerThread是在看AsyncQueryHandler源碼的時(shí)候,第一次眼看到HandlerThread這個(gè)名字,就在想這到底是個(gè)Handler還是個(gè)Thread,后來看了源碼才發(fā)現(xiàn)它其實(shí)就是一個(gè)Thread,只不過可以通過它的getLooper返回的Looper作為Handler構(gòu)造函數(shù)的參數(shù),那么這個(gè)新建Handler所有message的執(zhí)行都是在這個(gè)對(duì)應(yīng)的Thread當(dāng)中的,關(guān)于在項(xiàng)目當(dāng)中如何用好HandlerThreadAsyncQueryHandler無疑是一個(gè)最好的例子。

二、源碼

HandlerThread的代碼很少,我們一點(diǎn)點(diǎn)看:

int mPriority; //線程的優(yōu)先級(jí)
int mTid = -1; //線程對(duì)應(yīng)的pid,結(jié)束時(shí)為-1。
Looper mLooper; //對(duì)應(yīng)的looper

再看一下run方法:

<!-- HandlerThread.java -->
@Override
public  void run() {    
    mTid = Process.myTid();    
    Looper.prepare();    
    synchronized (this) {        
        mLooper = Looper.myLooper();         
        notifyAll();    
    }     
    Process.setThreadPriority(mPriority);     
    onLooperPrepared();    
    Looper.loop();    
    mTid = -1;
}

第一步先調(diào)用了Looper.prepare()方法來初始該線程的Looper,它其實(shí)是新建了一個(gè)Looper(這個(gè) Looper是可以退出循環(huán)的),保存到sThreadLocal變量當(dāng)中,這個(gè)變量是static final ThreadLocal<Looper>類型的,也就是每個(gè)Thread有一份不同的實(shí)例,并且在每個(gè)線程中也只允許初始化一次:

<!-- Looper.java -->

private Looper(boolean quitAllowed) {    
    mQueue = new MessageQueue(quitAllowed);    
    mThread = Thread.currentThread();
}

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));
}

之后拿出這個(gè)新建的Looper保存起來,同時(shí)設(shè)置線程的優(yōu)先級(jí),接著調(diào)用Looper.loop()方法,在這之前會(huì)回調(diào)給onLooperPrepared,因?yàn)槿绻?code>Looper.loop()開始執(zhí)行后,那么就是一個(gè)無限循環(huán),用戶沒法進(jìn)行初始化操作了:

<!-- Looper.java -->
public static void loop() {
     final Looper me = myLooper();
     if (me == null) {
         //exception;
     }
     final MessageQueue queue = me.mQueue;
     Binder.clearCallingIdentity();
     final long ident = Binder.clearCallingIdentity();
     for (;;) {
        //這里面會(huì)調(diào)用 nativePollOnce(ptr, nextPollTimeoutMillis) 等待有新的消息出現(xiàn),否則就一直阻塞在這里;
        Message msg = queue.next();
        //如果新的消息為null,那么就退出這個(gè)循環(huán)。
        if (message == null) {
            return;
        }
        //分發(fā)有效的消息。
        msg.target.dispatchMessage(msg);
        msg.recycleUnchecked();
     }
}

<!-- MessageQueue.java -->
Message next() {
   final long ptr = mPtr;
   if (ptr == null) {
      return null;
   }
   for (;;) {
       //會(huì)休眠在這里,直到通過mPtr被喚醒。 
       nativePollOnce(ptr, nextPollTimeoutMillis);
       //被喚醒后,從隊(duì)列中去除消息進(jìn)行處理。
   }
}

接下來看一下getLooper()方法,如果此時(shí)mLooper還沒有被初始化,那么它會(huì)一直休眠在這里:

<!-- HandlerThread.java -->
public Looper getLooper() {
    //如果線程沒有存活,那么退出。    
    if (!isAlive()) {        
        return null;    
    }          
    synchronized (this) {
        //如果線程存活,但是沒有開始運(yùn)行,那么等待。        
        while (isAlive() && mLooper == null) {             
            try {                
                wait();            
            } catch (InterruptedException e) {}         
        }    
     }    
     return mLooper;
}

這下來看一下退出的兩個(gè)方法,它們的區(qū)別就是safe方法會(huì)保證當(dāng)前隊(duì)列當(dāng)中的Message都被執(zhí)行完畢,但那些delay的消息不會(huì)被遞送。

<!-- HandlerThread.java -->
public boolean quit() {    
    Looper looper = getLooper();    
    if (looper != null) {        
        looper.quit();        
        return true;    
    }    
    return false;
}

public boolean quitSafely() {    
    Looper looper = getLooper();    
    if (looper != null) {        
        looper.quitSafely();        
        return true;    
    }    
    return false;
}

quit其實(shí)是調(diào)用了和Looper關(guān)聯(lián)的MessageQueue對(duì)應(yīng)的quit(boolean safe)方法:

<!-- Looper.java -->
public void quit() {    
    mQueue.quit(false);
}

public void quitSafely() {      
    mQueue.quit(true);
}

<!-- MessageQueue.java -->
void quit(boolean safe) {
    if (!mQuitAllowed) {    
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    
    synchronized (this) {    
        if (mQuitting) {        
            return;    
        }    
        mQuitting = true;    
        if (safe) {        
            removeAllFutureMessagesLocked();     
        } else {        
            removeAllMessagesLocked();    
        }
        //喚醒循環(huán),使得 loop 知道,mQuitting 的變化    
        nativeWake(mPtr);
    }
}

可以看到這里把mQuitting置為了true,之后通過nativeWake使Message.next()中 原先阻塞的 nativePollOnce繼續(xù)往下執(zhí)行并且會(huì)返回null,當(dāng)Looper.loop()中的queue.next()返回null ,那么 Looper.loop()也跟著退出了。
由此可見當(dāng)我們實(shí)例化一個(gè)HandlerThread對(duì)象,之后又調(diào)用了quitXXX方法,那么它就停止了這個(gè)循環(huán),相當(dāng)于這個(gè)Looper對(duì)象在這個(gè)線程中雖然存在,但是無法通過它做任何操作了,即使我們第二次執(zhí)行它的run方法,調(diào)用Looper.prepare()的時(shí)候,也會(huì)因?yàn)?code>sThreadLocal.get() != null而拋出異常。

三、結(jié)論

可以看到,HandlerThread就是創(chuàng)建了一個(gè)線程,當(dāng)我們調(diào)用它的start()方法時(shí),也就是執(zhí)行它的run方法,其實(shí)就是創(chuàng)建一個(gè)與他相關(guān)聯(lián)的Looper,并讓這個(gè)Looper跑起來,這個(gè)Looper中所有消息的處理都是在這個(gè)新建的線程當(dāng)中的,調(diào)用者可以通過這個(gè)Looper進(jìn)行后續(xù)的操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,676評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評(píng)論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,873評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,266評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,482評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,036評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,846評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,025評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,279評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評(píng)論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,751評(píng)論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,016評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容