HandlerThread和IntentService源碼解析

簡介

首先我們先來了解HandlerThread和IntentService是什么,以及為什么要將這兩者放在一起分析槽畔。
HandlerThread:

HandlerThread 其實是Handler + Thread + Looper的組合骤视,它本質上是一個Thread鞍爱,因為它繼承了Thread。相比普通的Thread专酗,它不會堵塞睹逃,因為他內部通過Looper實現(xiàn)了消息循環(huán)機制,保證了多個任務的串行執(zhí)行祷肯。缺點是效率比較低沉填,因為,串行執(zhí)行比起并行執(zhí)行佑笋,效率肯定會比較較低翼闹。

IntentService:

IntentService是繼承并處理異步請求的一個類,其本質上是一個Service蒋纬,因為他繼承了Service猎荠,所以開啟IntentService和普通的Service一致。但是他和普通的IntentService不同之處在于颠锉,他可以處理異步任務法牲,在任務處理完成之后會自動結束Service。另外我們可以啟動IntentService多次琼掠,而每一個耗時任務會已工作隊列的方式在IntentService的onHandleIntent回調方法中執(zhí)行拒垃,并且是串行執(zhí)行的。

好了在了解HandlerThread和IntentService分別是什么之后瓷蛙,我們來解決第二個問題悼瓮,那就是為什么我要將2者放在一起分析?其實IntentService的內部是通過HandlerThread和Handler來實現(xiàn)異步操作的艰猬,當我們了解了HandlerThread的使用和原理之后横堡,再去理解IntentService就會容易的多。好的冠桃,下面讓我們開始HandlerThread的源碼之旅命贴。

HandlerThread的使用和原理

HandlerThread的使用

這里我們要實現(xiàn)一個每隔5s更新TextView中的值的一個demo,源碼如下:

public class MainActivity extends AppCompatActivity {
  private static final String TAG = "MainActivity";
  private TextView mTv;
  private Button mBtn;
  HandlerThread mHandlerThread;
  Handler mThreadHandler;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initView();
    mBtn.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        initThread();
      }
    });
  }

  private void initView() {
    mTv = (TextView) findViewById(R.id.tv);
    mBtn = (Button) findViewById(R.id.btn);
  }

  private void initThread() {
    //創(chuàng)建一個HandlerThread并開啟線程
    mHandlerThread = new HandlerThread("update-msg");
    mHandlerThread.start();

    //從mHandlerThread中得到Looper并創(chuàng)建Handler
    mThreadHandler = new Handler(mHandlerThread.getLooper()) {
      @Override public void handleMessage(Message msg) {
        Log.v(TAG, "currentThread===>" + Thread.currentThread() + "   what====>" + msg.what);
        try {
          update();
        } catch (Exception e) {
          e.printStackTrace();
        }
        mThreadHandler.sendEmptyMessage(200);
      }
    };
    mThreadHandler.sendEmptyMessage(200);
  }

  private void update() throws Exception {
    Thread.sleep(3000);
    runOnUiThread(new Runnable() {
      @Override public void run() {
        String result = "每隔3s更新一次:";
        result += Math.random();
        mTv.setText(result);
      }
    });
  }
}

輸出的日志如下:

currentThread===>Thread[update-msg,5,main] what====>200

從日志我們可以看出handleMessage運行在我們創(chuàng)建的HandlerThread("update-msg")之下。我們有理由懷疑這跟我們傳入的mHandlerThread.getLooper()有關胸蛛。我們的mThreadHandler 是在UI線程中創(chuàng)建的污茵,按理來說handleMessage應該運行在UI線程中才對。了解Handler原理的都知道葬项,handleMessage方法是在Handler的dispatchMessage方法中被調用的且dispatchMessage方法沒有進行線程切換泞当。所以線程切換應該發(fā)生在dispatchMessage被調用的地方,那dispatchMessage是在哪被調用的呢民珍?我們發(fā)現(xiàn)Loop的loop()方法中調用了dispatchMessage()方法襟士。(這里我就不貼Handler和Loop相關的代碼,感興趣的可以參考我以前的文章:Handler的原理)而且我們還發(fā)現(xiàn)了loop()方法的注釋如下:

image.png

意思是loop()方法運行在Loop被綁定的線程中嚷量。

那Loop又是在什么時候被綁定的呢陋桂?

image.png

就是這2個方法對Loop進行了綁定。那這個sThreadLocal又是什么鬼津肛?它到底有什么用章喉?別急,我們去看下它創(chuàng)建的地方:
image.png

它其實就是一個ThreadLocal身坐,關于ThreadLocal的原理秸脱,大家可以參考:ThreadLocal源碼深入分析
在這里我簡單的說下,其實ThreadLocal的作用部蛇,就是通過Thread中的threadLocals:ThreadLocalMap變量將我們通過ThreadLocal#set方法傳進來的數(shù)據(jù)跟Thread進行綁定摊唇,從而保證了訪問到的變量屬于當前線程,每個線程都保存有一個變量副本涯鲁,每個線程的變量都不同巷查,而同一個線程在任何時候訪問這個本地變量的結果都是一致的。當此線程結束生命周期時抹腿,所有的線程本地實例都會被GC岛请。ThreadLocal相當于提供了一種線程隔離,將變量與線程相綁定警绩。ThreadLocal通常定義為private static類型崇败。

通過上面的分析,我們已經(jīng)知道了Handler#handleMessage方法會運行在Loop說綁定的線程上肩祥,驗證了我們一開始的猜想后室。這里Loop是從我們創(chuàng)建的HandlerThread中得到的,而HandlerThread其實就是一個線程混狠,所以Loop綁定在了新創(chuàng)建的HandlerThread上岸霹。但是我們并不滿足于此,我們得進一步看看HandlerThread和普通的Thread到底有什么不一樣将饺。

HandlerThread的源碼解析

HandlerThread的代碼量其實并不多贡避,它繼承于Thread痛黎,主要的方法其實就是run方法

@Override
    public void run() {
        mTid = Process.myTid();
        //創(chuàng)建Loop并綁定當前線程
        Looper.prepare();
        //關鍵代碼
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
      //設置線程優(yōu)先級
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // 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;
    }

短短的幾行代碼確幾乎實現(xiàn)了我們想要的所有功能,我們來看關鍵代碼run方法中的synchronized 代碼塊其實對應于getLooper方法中的synchronized代碼塊贸桶,這樣做的目的是為了確保舅逸,我們通過getLoop()方法得到的Loop對象一定是被初始化后的Loop。當Loop被初始化以后會調用抽象方法onLooperPrepared()皇筛,他一般被用于在開啟隊列循環(huán)之前做一些初始化的操作,然后執(zhí)行任務隊列坠七。

總結

HandlerThread的原理已經(jīng)分析完了水醋,我們來總結一下它的特點:

1.HandlerThread它就是一個線程,和開啟普通的線程得到操作一致
2.HandlerThread需要搭配Handler使用彪置,單獨使用的意義不大
3.HandlerThread會將通過handleMessage傳遞進來的任務進行串行執(zhí)行拄踪,這是由messageQueue的特性決定的,從而也說明了HandlerThread效率相比并行操作會比較低

IntentService的使用和原理

分析完HandlerThread之后我們來分析一下IntentService的使用和原理拳魁,老規(guī)矩我們先看怎么使用惶桐。

IntentService的使用

public class MyIntentService extends IntentService {
  private static final String TAG = "MyIntentService";

  public MyIntentService() {
    super("MyIntentService");
    Log.v(TAG,
        "MyIntentService===>MyIntentService()" + "  currentThread==>" + Thread.currentThread());
  }

  /**
   * Creates an IntentService.  Invoked by your subclass's constructor.
   *
   * @param name Used to name the worker thread, important only for debugging.
   */
  public MyIntentService(String name) {
    super(name);
    Log.v(TAG,
        "MyIntentService===>MyIntentService(name)" + "  currentThread==>" + Thread.currentThread());
  }

  @Override public void onCreate() {
    super.onCreate();
    Log.v(TAG, "MyIntentService===>onCreate" + "  currentThread==>" + Thread.currentThread());
  }

  @Override protected void onHandleIntent(@Nullable Intent intent) {
    Log.v(TAG, "MyIntentService===>onHandleIntent" + "  currentThread==>" + Thread.currentThread());
    try {
      Thread.sleep(10000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  @Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    Log.v(TAG, "MyIntentService===>onStartCommand" + "  currentThread==>" + Thread.currentThread());
    return super.onStartCommand(intent, flags, startId);
  }
}

調用服務和我們普通的Service一致
輸出的日志如下

MyIntentService===>MyIntentService() currentThread==>Thread[main,5,main]
MyIntentService===>onCreate currentThread==>Thread[main,5,main]
MyIntentService===>onStartCommand currentThread==>Thread[main,5,main]
MyIntentService===>onHandleIntent currentThread==>Thread[IntentService[MyIntentService],5,main]

從中我們可以看出onHandleIntent方法是運行在子線程中的,更有意思的是潘懊,當我們在onHandleIntent 方法中執(zhí)行延遲操作時姚糊,打印的日志如下描述:
1、當服務沒執(zhí)行完時又點擊了開啟服務的操作授舟,此時救恨,onStartCommand方法會立即執(zhí)行,而onHandleIntent方法會在上一個任務執(zhí)行完以后再去執(zhí)行onHandleIntent方法释树。
2肠槽、當服務已經(jīng)執(zhí)行完被自動結束以后,再去調用service奢啥,輸出的日志和第一次輸出的日志一致秸仙。

可能我說的比較抽象,大家自取去操作一遍就會發(fā)現(xiàn)我所說的有意思的地方桩盲。從上面的日志輸出寂纪,我們可以得出以下結論:
1、IntentService在任務執(zhí)行完以后會自動結束
2正驻、IntentService接收的任務是串行執(zhí)行的弊攘,并且互不干擾
3、IntentService的生命周期和普通的Service一致姑曙,只不過多了一個onHandleIntent回調方法襟交,并且它是串行回調的,等待上一個任務執(zhí)行完以后才會再次被調用

但是為什么會這樣呢伤靠?大家有沒有想過捣域。當然啼染,所有的答案都隱藏在源碼里,讓我們一起去揭開他神秘的面紗吧焕梅。

IntentService源碼解析

首先我們先來看下IntentService的幾個成員變量迹鹅,如下圖所示:


image.png

關于Loop和Handler我們都很熟悉了,前者是遍歷消息隊列的消息泵后者則是處理Handler發(fā)送過來的消息的贞言。下面我來看下他們初始化得到地方斜棚。
Loop初始化

image.png

原來他們都是在Service的onCreate回調方法中被初始化的。
通過上文HandlerThread的分析该窗,我們知道ServiceHandler的handleMessage方法會運行在mServiceLooper綁定的指定線程上弟蚀。這里這也就驗證了我們上文日志的輸出。

下面我們來解決另外一個問題酗失,也就是IntentService的生命周期函數(shù)的執(zhí)行情況义钉。
請看下面的代碼:


image.png

我們都知道當服務被啟動以后,再次調用服務的時候都會回調onStartCommand方法规肴,onStartCommand又調用了onStart方法捶闸,而onStart方法中只是通過Handler發(fā)送一個異步消息,然后ServiceHandler的handleMessage收到消息以后調用了onHandleIntent拖刃,這也就驗證了上文的日志輸出删壮。

下面我們來重點分析一下Service的stopSelf()方法,他有兩個重載方法序调,一個有參醉锅,一個無參,那他們之間有什么不同呢发绢?
我們還是通過源碼來看一下吧硬耍。


image.png

可以看到無參方法只是簡單的調用了有參方法,并傳入了一個-1的參數(shù)边酒。所以我們只有直接分析有參的方法就可以经柴。
由于Android sdk并沒有開放ActivityManageProxy(我們知道ActivityManage在客戶端得到代理是ActivityManageProxy)的代碼,所以我們只能通過查找相關資料來解決我們的疑惑墩朦。
最終我在官網(wǎng)上得到的答案如下:


image.png

簡單來說就是stopSelf中的startId對應于onStartCommand中的startId坯认,當stopSelf(startId)中的startId等于onStartCommand中的最后一個進來的startId的時候,就代表消息隊列中沒有更多的消息需要處理了氓涣,所以執(zhí)行完當前的消息以后牛哺,會去執(zhí)行Service的stop操作

總結

關于IntentService的分析到這就告一段落了劳吠,其實IntentService就是基于HandlerThread機制來實現(xiàn)的引润,它允許我們在onHandleIntent回調方法中執(zhí)行異步操作。同時要注意他的生命周期回調函數(shù)的差異痒玩。下面貼上官網(wǎng)上關于IntentService類的介紹淳附,幫助大家理解议慰。


image.png
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奴曙,隨后出現(xiàn)的幾起案子别凹,更是在濱河造成了極大的恐慌,老刑警劉巖洽糟,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炉菲,死亡現(xiàn)場離奇詭異,居然都是意外死亡脊框,警方通過查閱死者的電腦和手機颁督,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浇雹,“玉大人,你說我怎么就攤上這事屿讽≌蚜椋” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵伐谈,是天一觀的道長烂完。 經(jīng)常有香客問我,道長诵棵,這世上最難降的妖魔是什么抠蚣? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮履澳,結果婚禮上嘶窄,老公的妹妹穿的比我還像新娘。我一直安慰自己距贷,他們只是感情好柄冲,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著忠蝗,像睡著了一般现横。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阁最,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天戒祠,我揣著相機與錄音,去河邊找鬼速种。 笑死姜盈,一個胖子當著我的面吹牛,可吹牛的內容都是我干的哟旗。 我是一名探鬼主播贩据,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼栋操,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饱亮?” 一聲冷哼從身側響起矾芙,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎近上,沒想到半個月后剔宪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡壹无,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年葱绒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斗锭。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡地淀,死狀恐怖,靈堂內的尸體忽然破棺而出岖是,到底是詐尸還是另有隱情帮毁,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布豺撑,位于F島的核電站烈疚,受9級特大地震影響,放射性物質發(fā)生泄漏聪轿。R本人自食惡果不足惜爷肝,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陆错。 院中可真熱鬧灯抛,春花似錦、人聲如沸危号。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽外莲。三九已至猪半,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間偷线,已是汗流浹背磨确。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留声邦,地道東北人乏奥。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像亥曹,于是被迫代替她去往敵國和親邓了。 傳聞我的和親對象是個殘疾皇子恨诱,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內容