Android - IntentService 介紹和源碼分析

IntentService 是系統(tǒng)提供的一種 Service,它用 Intent 傳遞數(shù)據(jù)单默,用子線程執(zhí)行代碼。使用時需要繼承 IntentService,自定義 Intent 中攜帶的數(shù)據(jù)格式以及對應(yīng)的執(zhí)行代碼褂傀,外部調(diào)用時需要按照格式定義 Intent 數(shù)據(jù)并啟動服務(wù)。

什么時候用

Service 作為四大組件之一加勤,用來實現(xiàn)與 UI 無關(guān)的功能仙辟,但 Service 各個生命周期的方法仍然運行在 UI 線程中,這就需要將任務(wù)提交到子線程中執(zhí)行一些耗時任務(wù)鳄梅。IntentService 就是安卓系統(tǒng)提供的一種使用子線程執(zhí)行任務(wù)的 Service叠国。

IntentService 將任務(wù)提交到一個 worker 線程中,使用者只需要定義好 Intent 數(shù)據(jù)格式戴尸,實現(xiàn)解析 Intent 和執(zhí)行任務(wù)的代碼即可粟焊,使用起來非常簡單。同時 IntentService 保證多個任務(wù)在 worker 線程中按提交順序串行執(zhí)行孙蒙。這個 worker 線程不會一直存在项棠,一旦任務(wù)執(zhí)行完畢,IntentService 就會自行銷毀挎峦。

因此香追,IntentService

適合:

  • 需要串行運行的任務(wù)
  • 輕量級耗時任務(wù),不能在 UI 線程執(zhí)行坦胶,但多個任務(wù)順序執(zhí)行不會阻塞

不適合:

  • 需要并行運行的任務(wù)透典,比如下載多個文件
  • 需要一直運行的任務(wù),比如監(jiān)聽網(wǎng)絡(luò)請求
  • 非常耗時的任務(wù)顿苇,會阻塞后面提交的任務(wù)

使用步驟

  1. 定義一個 Service 繼承自 IntentService峭咒。
public class MyIntentService extends IntentService {
}
  1. 定義數(shù)據(jù)格式,將其封裝在 Intent 中岖圈。
public static void startSave(Context context, String data, String name, int count) {
    Intent intent = new Intent(context, MyIntentService.class);
    intent.putExtra("data", data);
    intent.putExtra("name", name);
    intent.putExtra("count", count);
    context.startService(intent);
}
  1. 實現(xiàn) protected void onHandleIntent(Intent intent) 方法
    1. 解析 Intent 中的參數(shù)
    2. 根據(jù)參數(shù)執(zhí)行任務(wù)
@Override
protected void onHandleIntent(@Nullable Intent intent) {
    int function = intent.getIntExtra("function", 0);
    switch (function) {
        case FUNCTION_SAVE:
            String data = intent.getStringExtra("data");
            String name = intent.getStringExtra("name");
            int count = intent.getIntExtra("count", 0);
            doFunction(data, name, count);
            break;
    }
}
  1. 在需要執(zhí)行任務(wù)的地方讹语,啟動服務(wù)
MyIntentService.startSave(this, "569585687455236541789355", "image", 1);

源碼分析

HandlerThread

在分析 IntentService 的源碼之前,先簡單介紹一下 HandlerThread蜂科,因為這是 IntentService 的核心顽决。

從名字可以看出來 HandlerThread 是一個線程短条,一個帶 Handler 的線程,可以通過 Handler 發(fā)送消息與線程通信才菠。它的源碼也非常簡單茸时,去掉注釋不到一百行,下面簡單分析一下 run() 方法和 getLooper() 方法赋访。

下面是 run() 方法和 getLooper() 方法的簡化版源碼可都,刪除了一些不太重要的代碼。

public void run() {
    Looper.prepare(); // Looper 使用第一步 prepare()
    synchronized (this) { // 這里鎖一下蚓耽,保護 mLooper 變量渠牲。
        mLooper = Looper.myLooper(); // 獲得 Looper 引用
        notifyAll();
    }
    Looper.loop(); // Looper 使用最后一步,開始無限消息循環(huán)
}
public Looper getLooper() {
    // 線程 alive 狀態(tài)才有意義步悠,所以必須先調(diào)用線程的 start()签杈,再調(diào)用這個方法。
    if (!isAlive()) {
        return null;
    }
    // 這里的 synchronized 鎖對應(yīng) run() 方法中的 synchronized 鎖鼎兽。
    // run() 方法中對 mLooper 的初始化代碼運行在這個 HandlerThread 中答姥,而 getLooper() 方法讀取 
    // mLooper 變量運行在另一個線程中。沒錯谚咬,這就是一個簡單的生產(chǎn)者/消費者的場景鹦付。
    synchronized (this) {
        // 判斷 alive 考慮 while 循環(huán)運行過程中線程中止的情況。
        while (isAlive() && mLooper == null) { // 循環(huán)套 wait() 择卦,下面詳細解釋敲长。
            try {
                wait(); // 等待 mLooper 初始化完成后的 notify
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}

循環(huán)套 wait() 的寫法,是并發(fā)代碼的標準套路互捌。

  • wait() 是為了等待一個條件是否滿足潘明,這里是等待 mLooper 可用行剂。但觸發(fā) wait() 繼續(xù)執(zhí)行的 notify() 卻并不一定是因為準備好了 mLooper 才執(zhí)行的秕噪。wait() 繼續(xù)執(zhí)行了不代表條件就滿足了,因此要循環(huán)重復判斷條件才能確保條件是真的滿足了厚宰。
  • wait() 還可能拋出 InterruptedException腌巾,例如被 Thread.interrupt() 中斷,這時等待的條件不一定被滿足铲觉,需要重復檢查澈蝙。

可以看 Java 官方文檔有詳細的解釋,講得非常好:Guarded Blocks

IntentService.onCreat()

初始化 worker 線程撵幽,也就是 HandlerThread灯荧。可見 IntentService 的核心是通過 Handler 消息機制來與 worker 線程通信盐杂。這幾句代碼也是 HandlerThread 的一個典型使用步驟逗载。

@Override
public void onCreate() {
    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper); // 自定義 Handler 處理消息
}

IntentService.onStart()

每次啟動服務(wù)就是一次任務(wù)提交哆窿,任務(wù)自定義的數(shù)據(jù)和參數(shù)都封裝在了 Intent 對象中,這個 Intent 對象被封裝到了 Message 對象中厉斟,傳遞給 worker 線程挚躯。

@Override
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

注意還有個 startId 也保存到了 Message 中,這是用來自動停止服務(wù)用的擦秽,可以參考上一篇文章有詳細說明 startId 如何用來停止 Service:Android - Service Start/Stop 使用

ServiceHandler

HandlerThread 的 Handler码荔,handleMessage() 方法在 worker 線程中執(zhí)行。調(diào)用 IntentService 子類的抽象方法 onHandleIntent() 來自定義業(yè)務(wù)邏輯感挥。

private final class ServiceHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj);
        stopSelf(msg.arg1);
    }
}

運行完成之后缩搅,執(zhí)行 stopSelf(startId) 結(jié)束本次任務(wù),注意是本次任務(wù)而不是整個服務(wù)触幼。參數(shù) startId 的意義就在于它代表了一次任務(wù)的提交誉己,使 stopSelf 方法不會直接停止服務(wù),而是停止一個任務(wù)域蜗。內(nèi)部實現(xiàn)是用最后一個 startId 來判斷的巨双,只有這個 startId 是最后一個通過 onStartCommand 傳遞來的 startId,stopSelf(startId) 才會停止任務(wù)霉祸,否則忽略筑累。

IntentService 自動停止和串行的原理

  • 因為 stopSelf(startId) 保證所有提交的任務(wù)處理完成后才停止服務(wù),使得 IntentService 在提交任務(wù)速度大于任務(wù)處理速度的時候丝蹭,能保持服務(wù)持續(xù)運行慢宗,不會重復創(chuàng)建多個服務(wù)浪費資源。
  • 得益于 Handler 消息可以串行處理奔穿,IntentService 處理任務(wù)是串行執(zhí)行在 worker 線程中的镜沽。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贱田,隨后出現(xiàn)的幾起案子缅茉,更是在濱河造成了極大的恐慌,老刑警劉巖男摧,帶你破解...
    沈念sama閱讀 223,126評論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔬墩,死亡現(xiàn)場離奇詭異,居然都是意外死亡耗拓,警方通過查閱死者的電腦和手機拇颅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評論 3 400
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乔询,“玉大人樟插,你說我怎么就攤上這事。” “怎么了黄锤?”我有些...
    開封第一講書人閱讀 169,941評論 0 366
  • 文/不壞的土叔 我叫張陵麻献,是天一觀的道長。 經(jīng)常有香客問我猜扮,道長勉吻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,294評論 1 300
  • 正文 為了忘掉前任旅赢,我火速辦了婚禮齿桃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘煮盼。我一直安慰自己短纵,他們只是感情好,可當我...
    茶點故事閱讀 69,295評論 6 398
  • 文/花漫 我一把揭開白布僵控。 她就那樣靜靜地躺著香到,像睡著了一般。 火紅的嫁衣襯著肌膚如雪报破。 梳的紋絲不亂的頭發(fā)上悠就,一...
    開封第一講書人閱讀 52,874評論 1 314
  • 那天,我揣著相機與錄音充易,去河邊找鬼梗脾。 笑死,一個胖子當著我的面吹牛盹靴,可吹牛的內(nèi)容都是我干的炸茧。 我是一名探鬼主播,決...
    沈念sama閱讀 41,285評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼稿静,長吁一口氣:“原來是場噩夢啊……” “哼梭冠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起改备,我...
    開封第一講書人閱讀 40,249評論 0 277
  • 序言:老撾萬榮一對情侶失蹤控漠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后绍妨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體润脸,經(jīng)...
    沈念sama閱讀 46,760評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,840評論 3 343
  • 正文 我和宋清朗相戀三年他去,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倒堕。...
    茶點故事閱讀 40,973評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡灾测,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情媳搪,我是刑警寧澤铭段,帶...
    沈念sama閱讀 36,631評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站秦爆,受9級特大地震影響序愚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜等限,卻給世界環(huán)境...
    茶點故事閱讀 42,315評論 3 336
  • 文/蒙蒙 一爸吮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧望门,春花似錦形娇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厨剪,卻和暖如春哄酝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祷膳。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評論 1 275
  • 我被黑心中介騙來泰國打工炫七, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钾唬。 一個月前我還...
    沈念sama閱讀 49,431評論 3 379
  • 正文 我出身青樓万哪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親抡秆。 傳聞我的和親對象是個殘疾皇子奕巍,可洞房花燭夜當晚...
    茶點故事閱讀 45,982評論 2 361

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