Android多線程:這是一份詳細的HandlerThread源碼分析攻略


前言

  • 多線程的應用在Android開發中是非常常見的,常用方法主要有:

    1. 繼承Thread類
    2. 實現Runnable接口
    3. Handler
    4. AsyncTask
    5. HandlerThread
    6. IntentService
  • 今天,我將全面解析多線程中 HandlerThread的源碼

Carson帶你學多線程系列
基礎匯總
Android多線程:基礎知識匯總
基礎使用
Android多線程:繼承Thread類使用(含實例教程)
Android多線程:實現Runnable接口使用(含實例教程)
復合使用
Android多線程:AsyncTask使用教程(含實例講解)
Android多線程:AsyncTask原理及源碼分析
Android多線程:HandlerThread使用教程(含實例講解)
Android多線程:HandlerThread原理及源碼分析
Android多線程:IntentService使用教程(含實例講解)
Android多線程:IntentService的原理及源碼分析
Android多線程:線程池ThreadPool全方位教學
相關使用
Android異步通信:這是一份全面&詳細的Handler機制學習攻略
Android多線程:手把手教你全面學習神秘的Synchronized關鍵字
Android多線程:帶你了解神秘的線程變量 ThreadLocal


目錄

示意圖

1. 簡介

示意圖

更加具體請看文章:


2. 源碼分析

  • 本次源碼分析將根據 HandlerThread的使用步驟講解

若不熟悉,請務必看文章Android多線程:手把手教你使用HandlerThread

  • HandlerThread的使用步驟有5個:
// 步驟1:創建HandlerThread實例對象
// 傳入參數 = 線程名字,作用 = 標記該線程
   HandlerThread mHandlerThread = new HandlerThread("handlerThread");

// 步驟2:啟動線程
   mHandlerThread.start();

// 步驟3:創建工作線程Handler & 復寫handleMessage()
// 作用:關聯HandlerThread的Looper對象、實現消息處理操作 & 與其他線程進行通信
// 注:消息處理操作(HandlerMessage())的執行線程 = mHandlerThread所創建的工作線程中執行
  Handler workHandler = new Handler( mHandlerThread.getLooper() ) {
            @Override
            public boolean handleMessage(Message msg) {
                ...//消息處理
                return true;
            }
        });

// 步驟4:使用工作線程Handler向工作線程的消息隊列發送消息
// 在工作線程中,當消息循環時取出對應消息 & 在工作線程執行相關操作
  // a. 定義要發送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的標識
  msg.obj = "B"; // 消息的存放
  // b. 通過Handler發送消息到其綁定的消息隊列
  workHandler.sendMessage(msg);

// 步驟5:結束線程,即停止線程的消息循環
  mHandlerThread.quit();
  • 下面,我將根據上述使用步驟進行源碼分析

步驟1:創建HandlerThread的實例對象

/**
  * 具體使用
  * 傳入參數 = 線程名字,作用 = 標記該線程
  */ 
   HandlerThread mHandlerThread = new HandlerThread("handlerThread");

/**
  * 源碼分析:HandlerThread類的構造方法
  */ 
    public class HandlerThread extends Thread {
    // 繼承自Thread類
        
        int mPriority; // 線程優先級
        int mTid = -1; // 當前線程id
        Looper mLooper; // 當前線程持有的Looper對象

       // HandlerThread類有2個構造方法
       // 區別在于:設置當前線程的優先級參數,即可自定義設置 or 使用默認優先級

            // 方式1. 默認優先級
            public HandlerThread(String name) {
                // 通過調用父類默認的方法創建線程
                super(name);
                mPriority = Process.THREAD_PRIORITY_DEFAULT;
            }
          
            // 方法2. 自定義設置優先級
            public HandlerThread(String name, int priority) {
                super(name);
                mPriority = priority;
            }
            ...
     }

總結

  • HandlerThread類繼承自Thread
  • 創建HandlerThread類對象 = 創建Thread類對象 + 設置線程優先級 = 新開1個工作線程 + 設置線程優先級

步驟2:啟動線程

/**
  * 具體使用
  */ 
   mHandlerThread.start();

/**
  * 源碼分析:此處調用的是父類(Thread類)的start(),最終回調HandlerThread的run()
  */ 
  @Override
    public void run() {
        // 1. 獲得當前線程的id
        mTid = Process.myTid();

        // 2. 創建1個Looper對象 & MessageQueue對象
        Looper.prepare();

        // 3. 通過持有鎖機制來獲得當前線程的Looper對象
        synchronized (this) {
            mLooper = Looper.myLooper();
           
            // 發出通知:當前線程已經創建mLooper對象成功
            // 此處主要是通知getLooper()中的wait()
            notifyAll();
            
            // 此處使用持有鎖機制 + notifyAll() 是為了保證后面獲得Looper對象前就已創建好Looper對象
        }

        // 4. 設置當前線程的優先級
        Process.setThreadPriority(mPriority);

        // 5. 在線程循環前做一些準備工作 ->>分析1
        // 該方法實現體是空的,子類可實現 / 不實現該方法
        onLooperPrepared();

        // 6. 進行消息循環,即不斷從MessageQueue中取消息 & 派發消息
        Looper.loop();

        mTid = -1;
    }
}

/**
  * 分析1:onLooperPrepared();
  * 說明:該方法實現體是空的,子類可實現 / 不實現該方法
  */ 
    protected void onLooperPrepared() {

    }

總結

  1. 為當前工作線程(即步驟1創建的線程)創建1個Looper對象 & MessageQueue對象
  2. 通過持有鎖機制來獲得當前線程的Looper對象
  3. 發出通知:當前線程已經創建mLooper對象成功
  4. 工作線程進行消息循環,即不斷從MessageQueue中取消息 & 派發消息

步驟3:創建工作線程Handler & 復寫handleMessage()

/**
  * 具體使用
  * 作用:將Handler關聯HandlerThread的Looper對象、實現消息處理操作 & 與其他線程進行通信
  * 注:消息處理操作(HandlerMessage())的執行線程 = mHandlerThread所創建的工作線程中執行
  */ 
   Handler workHandler = new Handler( handlerThread.getLooper() ) {
            @Override
            public boolean handleMessage(Message msg) {
                ...//消息處理
                return true;
            }
        });

/**
  * 源碼分析:handlerThread.getLooper()
  * 作用:獲得當前HandlerThread線程中的Looper對象
  */ 
    public Looper getLooper() {
        // 若線程不是存活的,則直接返回null
        if (!isAlive()) {
            return null;
        } 
        // 若當前線程存活,再判斷線程的成員變量mLooper是否為null
        // 直到線程創建完Looper對象后才能獲得Looper對象,若Looper對象未創建成功,則阻塞
        synchronized (this) {
  
      
            while (isAlive() && mLooper == null) {
                try {
                    // 此處會調用wait方法去等待
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        // 上述步驟run()使用 持有鎖機制 + notifyAll()  獲得Looper對象后
        // 則通知當前線程的wait()結束等待 & 跳出循環
        // 最終getLooper()返回的是在run()中創建的mLooper對象
        return mLooper;
    }

總結

  • 在獲得HandlerThread工作線程的Looper對象時存在一個同步的問題:只有當線程創建成功 & 其對應的Looper對象也創建成功后才能獲得Looper的值,才能將創建的Handler 與 工作線程的Looper對象綁定,從而將Handler綁定工作線程
  • 解決方案:即保證同步的解決方案 = 同步鎖、wait()notifyAll(),即 在run()中成功創建Looper對象后,立即調用notifyAll()通知 getLooper()中的wait()結束等待 & 返回run()中成功創建的Looper對象,使得Handler與該Looper對象綁定

步驟4:使用工作線程Handler向工作線程的消息隊列發送消息

/**
  * 具體使用
  * 作用:在工作線程中,當消息循環時取出對應消息 & 在工作線程執行相關操作
  * 注:消息處理操作(HandlerMessage())的執行線程 = mHandlerThread所創建的工作線程中執行
  */ 
  // a. 定義要發送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的標識
  msg.obj = "B"; // 消息的存放
  // b. 通過Handler發送消息到其綁定的消息隊列
  workHandler.sendMessage(msg);

/**
  * 源碼分析:workHandler.sendMessage(msg)
  * 此處的源碼即Handler的源碼,故不作過多描述
  */ 

步驟5:結束線程,即停止線程的消息循環

/**
  * 具體使用
  */ 
  mHandlerThread.quit();

/**
  * 源碼分析:mHandlerThread.quit()
  * 說明:
  *     a. 該方法屬于HandlerThread類
  *     b. HandlerThread有2種讓當前線程退出消息循環的方法:quit() 、quitSafely()
  */ 
    
  // 方式1:quit() 
  // 特點:效率高,但線程不安全
  public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit(); 
            return true;
        }
        return false;
    }

  // 方式2:quitSafely()
  // 特點:效率低,但線程安全
  public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

  // 注:上述2個方法最終都會調用MessageQueue.quit(boolean safe)->>分析1

/**
  * 分析1:MessageQueue.quit(boolean safe)
  */ 
    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(); // 方式1(安全)會調用該方法 ->>分析3
            } else {
                removeAllMessagesLocked(); // 方式2(不安全)會調用該方法 ->>分析2
            }
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
/**
  * 分析2:removeAllMessagesLocked()
  * 原理:遍歷Message鏈表、移除所有信息的回調 & 重置為null
  */ 
  private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}
/**
  * 分析3:removeAllFutureMessagesLocked() 
  * 原理:先判斷當前消息隊列是否正在處理消息
  *      a. 若不是,則類似分析2移除消息
  *      b. 若是,則等待該消息處理處理完畢再使用分析2中的方式移除消息退出循環
  * 結論:退出方法安全與否(quitSafe() 或 quit()),在于該方法移除消息、退出循環時是否在意當前隊列是否正在處理消息
  */ 
  private void removeAllFutureMessagesLocked() {

    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;

    if (p != null) {
        // 判斷當前消息隊列是否正在處理消息
        // a. 若不是,則直接移除所有回調
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {
        // b. 若是正在處理,則等待該消息處理處理完畢再退出該循環
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

至此,關于HandlerThread源碼的分析完畢。


3. 總結

  • 本文全面分析了多線程中HandlerThread的源碼,總結如下

    示意圖

  • 下一篇文章我將對講解Android多線程的相關知識,感興趣的同學可以繼續關注Carson_Ho的簡書

Carson帶你學多線程系列
基礎匯總
Android多線程:基礎知識匯總
基礎使用
Android多線程:繼承Thread類使用(含實例教程)
Android多線程:實現Runnable接口使用(含實例教程)
復合使用
Android多線程:AsyncTask使用教程(含實例講解)
Android多線程:AsyncTask原理及源碼分析
Android多線程:HandlerThread使用教程(含實例講解)
Android多線程:HandlerThread原理及源碼分析
Android多線程:IntentService使用教程(含實例講解)
Android多線程:IntentService的原理及源碼分析
Android多線程:線程池ThreadPool全方位教學
相關使用
Android異步通信:這是一份全面&詳細的Handler機制學習攻略
Android多線程:手把手教你全面學習神秘的Synchronized關鍵字
Android多線程:帶你了解神秘的線程變量 ThreadLocal


歡迎關注Carson_Ho的簡書

不定期分享關于安卓開發的干貨,追求短、平、快,但卻不缺深度


請點贊!因為你的鼓勵是我寫作的最大動力!

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

推薦閱讀更多精彩內容