詳解Android中IntentService的使用方法
為什么我們需要IntentService ?
Android中的IntentService是繼承自Service類的,在我們討論IntentService之前,我們先想一下Service的特點: Service的回調(diào)方法(onCreate、onStartCommand、onBind、onDestroy)都是運行在主線程中的。當(dāng)我們通過startService啟動Service之后,我們就需要在Service的onStartCommand方法中寫代碼完成工作,但是onStartCommand是運行在主線程中的,如果我們需要在此處完成一些網(wǎng)絡(luò)請求或IO等耗時操作,這樣就會阻塞主線程UI無響應(yīng),從而出現(xiàn)ANR現(xiàn)象。為了解決這種問題,最好的辦法就是在onStartCommand中創(chuàng)建一個新的線程,并把耗時代碼放到這個新線程中執(zhí)行??梢詤⒖贾暗奈恼?a target="_blank" href="http://www.dbjr.com.cn/article/76476.htm">《Android通過startService實現(xiàn)文件批量下載》,這篇文章在onStartCommand中開啟了新的線程作為工作線程去執(zhí)行網(wǎng)絡(luò)請求,所以這樣不會阻塞主線程。由此看來,創(chuàng)建一個帶有工作線程的Service是一種很常見的需求(因為工作線程不會阻塞主線程),所以Android為了簡化開發(fā)帶有工作線程的Service,Android額外開發(fā)了一個類——–IntentService。
IntentService的特點
IntentService具有以下特點:
- 1. IntentService自帶一個工作線程,當(dāng)我們的Service需要做一些可能會阻塞主線程的工作的時候可以考慮使用IntentService。
- 2. 我們需要將要做的實際工作放入到IntentService的onHandleIntent回到方法中,當(dāng)我們通過startService(intent)啟動了IntentService之后,最終Android Framework會回調(diào)其onHandleIntent方法,并將intent傳入該方法,這樣我們就可以根據(jù)intent去做實際工作,并且onHandleIntent運行在IntentService所持有的工作線程中,而非主線程。
- 3. 當(dāng)我們通過startService多次啟動了IntentService,這會產(chǎn)生多個job,由于IntentService只持有一個工作線程,所以每次onHandleIntent只能處理一個job。面多多個job,IntentService會如何處理?處理方式是one-by-one,也就是一個一個按照先后順序處理,先將intent1傳入onHandleIntent,讓其完成job1,然后將intent2傳入onHandleIntent,讓其完成job2…這樣直至所有job完成,所以我們IntentService不能并行的執(zhí)行多個job,只能一個一個的按照先后順序完成,當(dāng)所有job完成的時候IntentService就銷毀了,會執(zhí)行onDestroy回調(diào)方法。
如何使用IntentService ?
在《Android通過startService實現(xiàn)文件批量下載》一文中,我們演示了如何通過Service批量下載文章,現(xiàn)在在本文中我們還是要演示如何批量下載文章,只不過是用IntentService完成這項工作。
系統(tǒng)界面如下:
界面很簡單,就一個按鈕“批量下載文章”,通過該Activity上的按鈕啟動DownloadService。
代碼如下:
package com.ispring.startservicedemo; import android.app.IntentService; import android.content.Intent; import android.util.Log; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class DownloadIntentService extends IntentService { public DownloadIntentService(){ super("Download"); Log.i("DemoLog", "DownloadIntentService構(gòu)造函數(shù), Thread: " + Thread.currentThread().getName()); } @Override public void onCreate() { super.onCreate(); Log.i("DemoLog", "DownloadIntentService -> onCreate, Thread: " + Thread.currentThread().getName()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("DemoLog", "DownloadIntentService -> onStartCommand, Thread: " + Thread.currentThread().getName() + " , startId: " + startId); return super.onStartCommand(intent, flags, startId); } @Override protected void onHandleIntent(Intent intent) { HttpURLConnection conn = null; InputStream is = null; String blogUrl = intent.getStringExtra("url"); String blogName = intent.getStringExtra("name"); try{ //下載指定的文件 URL url = new URL(blogUrl); conn = (HttpURLConnection)url.openConnection(); if(conn != null){ //我們在此處得到所下載文章的輸入流,可以將其以文件的形式寫入到存儲卡上面或 //將其讀取出文本顯示在App中 is = conn.getInputStream(); } }catch (MalformedURLException e){ e.printStackTrace(); }catch (IOException e){ e.printStackTrace(); }finally { if(conn != null){ conn.disconnect(); } } Log.i("DemoLog", "DownloadIntentService -> onHandleIntent, Thread: " + Thread.currentThread().getName() + ", 《" + blogName + "》下載完成"); } @Override public void onDestroy() { super.onDestroy(); Log.i("DemoLog", "DownloadIntentService -> onDestroy, Thread: " + Thread.currentThread().getName()); } }
DownloadActivity的代碼如下:
package com.ispring.startservicedemo; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class DownloadActivity extends Activity implements Button.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_download); } @Override public void onClick(View v) { List<String> list = new ArrayList<>(); list.add("Android通過startService播放背景音樂;http://www.dbjr.com.cn/article/76479.htm"); Iterator iterator = list.iterator(); while (iterator.hasNext()){ String str = (String)iterator.next(); String[] splits = str.split(";"); String name = splits[0]; String url = splits[1]; Intent intent = new Intent(this, DownloadIntentService.class); intent.putExtra("name", name); intent.putExtra("url", url); //啟動IntentService startService(intent); } } }
當(dāng)我們單擊了按鈕“批量下載文章”時,我們會多次調(diào)用Activity的startService方法,其中我們在其參數(shù)intent中存儲了文章名name以及文章的地址url,由于我們多次調(diào)用了startService方法,所以會批量下載文章。
點擊按鈕后,控制臺運行結(jié)果如下所示:
通過以上的輸出結(jié)果我們可以發(fā)現(xiàn),DownloadIntentService的onCreate、onStartCommand、onDestroy回調(diào)方法都是運行在主線程中的,而onHandleIntent是運行在工作線程IntentService[Download]中的,這驗證了我們上面所說的IntentService的第一個和第二個特點。
通過上面的輸出結(jié)果我們還會發(fā)現(xiàn),在我們連續(xù)調(diào)用了五次startService(intent)之后,onStartCommand依次被調(diào)用了五次,然后依次執(zhí)行了onHandleIntent五次,這樣就依次完成了job,當(dāng)最后一個job完成,也就是在最后一次onHandleIntent調(diào)用完成之后,整個IntentService的工作都完成,執(zhí)行onDestroy回調(diào)方法,IntentService銷毀。
IntentService工作原理及源碼解析
在上面我們已經(jīng)介紹了IntentService的特點以及如何使用,那么你可能會疑問Android是如何將調(diào)度這些intent將其傳入onHandleIntent完成工作的,其實IntentService的工作原理很簡單,將intent轉(zhuǎn)換為Message并放到消息隊列中,然后讓Handler依次從中取出Message對其進(jìn)行處理。
IntentService的源碼如下:
package android.app; import android.content.Intent; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; public abstract class IntentService extends Service { private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private String mName; private boolean mRedelivery; private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { //在工作線程中調(diào)用onHandleIntent,確保onHandleIntent不會阻塞主線程 onHandleIntent((Intent)msg.obj); //在執(zhí)行完了onHandleIntent之后,我們需要調(diào)用stopSelf(startId)聲明某個job完成了 //當(dāng)所有job完成的時候,Android就會回調(diào)onDestroy方法,銷毀IntentService stopSelf(msg.arg1); } } public IntentService(String name) { //此處的name將用作線程名稱 super(); mName = name; } public void setIntentRedelivery(boolean enabled) { mRedelivery = enabled; } @Override public void onCreate() { super.onCreate(); //創(chuàng)建HandlerThread,利用mName作為線程名稱,HandlerThread是IntentService的工作線程 HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); //將創(chuàng)建的HandlerThread所綁定的looper對象傳遞給ServiceHandler, //這樣我們創(chuàng)建的Handler就和HandlerThread通過消息隊列綁定在了一起 mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public void onStart(Intent intent, int startId) { //在此方法中創(chuàng)建Message對象,并將intent作為Message的obj參數(shù), //這樣Message與Intent就關(guān)聯(lián)起來了 Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; //將關(guān)聯(lián)了Intent信息的Message發(fā)送給Handler mServiceHandler.sendMessage(msg); } @Override public int onStartCommand(Intent intent, int flags, int startId) { //IntentService重寫了onStartCommand回調(diào)方法:在內(nèi)部調(diào)用onStart回調(diào)方法 //所以我們在繼承IntentService時,不應(yīng)該再覆寫該方法,即便覆蓋該方法,我們也應(yīng)該調(diào)用super.onStartCommand() onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; } @Override public void onDestroy() { //在onDestroy方法中調(diào)用了Handler的quit方法,該方法會終止消息循環(huán) mServiceLooper.quit(); } @Override public IBinder onBind(Intent intent) { return null; } protected abstract void onHandleIntent(Intent intent); }
我對上面的代碼已經(jīng)加了很多注釋,相信大家直接看代碼就能理解IntentService是如何運作的了。
IntentService繼承自Service類,并且IntentService重寫了onCreate、onStartCommand、onStart、onDestroy回調(diào)方法,并且IntentService還添加了一個onHandleIntent回調(diào)方法。下面我們依次解釋這幾個方法在IntentService的作用。
onCreate: 在onCreate回調(diào)方法中,利用mName作為線程名稱,創(chuàng)建HandlerThread,HandlerThread是IntentService的工作線程。HandlerThread在執(zhí)行了start方法之后,其本身就關(guān)聯(lián)了消息隊列和Looper,并且消息隊列開始循環(huán)起來。
onStartCommand: IntentService重寫了onStartCommand回調(diào)方法:在內(nèi)部調(diào)用onStart回調(diào)方法。
onStart: 在onStart方法中創(chuàng)建Message對象,并將intent作為Message的obj參數(shù),這樣Message與Intent就關(guān)聯(lián)起來了,然后通過Handler的sendMessage方法將關(guān)聯(lián)了Intent信息的Message發(fā)送給Handler。
onHandleIntent: 當(dāng)在onStart方法中,通過sendMessage方法將Message放入到Handler所關(guān)聯(lián)的消息隊列中后,Handler所關(guān)聯(lián)的Looper對象會從消息隊列中取出一個Message,然后將其傳入Handler的handleMessage方法中,在handleMessage方法中首先通過Message的obj獲取到了原始的Intent對象,然后將其作為參數(shù)傳給了onHandleIntent方法讓其執(zhí)行。handleMessage方法是運行在HandlerThread的,所以onHandleIntent也是運行在工作線程中的。在執(zhí)行完了onHandleIntent之后,我們需要調(diào)用stopSelf(startId)聲明某個job完成了。當(dāng)所有job完成的時候,Android就會回調(diào)onDestroy方法,銷毀IntentService。
onDestroy: 當(dāng)所有job完成的時候,Service會銷毀并執(zhí)行其onDestroy回調(diào)方法。在該方法中,調(diào)用了Handler的quit方法,該方法會終止消息循環(huán)。
總結(jié)
IntentService可以在工作線程中完成工作而不阻塞主線程,但是IntentService不能并行處理多個job,只能依次處理,一個接一個,當(dāng)所有的job完成后,會自動執(zhí)行onDestroy方法而無需我們自己調(diào)用stopSelf()或stopSelf(startId)方法。IntentService并不神秘,只是Android對一種常見開發(fā)方式的封裝,便于開發(fā)人員減少開發(fā)工作量。 IntentService是個助手類,如果Android沒有提供該類也沒什么,我們自己也可以寫一個類似的。IntentService之余Service,類似于HandlerThread之于Handler。
希望本文對大家理解IntentService有所幫助。
- Android使用Intent.ACTION_SEND分享圖片和文字內(nèi)容的示例代碼
- Android編程實現(xiàn)自定義分享列表ACTION_SEND功能的方法
- android中Intent傳值與Bundle傳值的區(qū)別詳解
- android教程之intent的action屬性使用示例(intent發(fā)短信)
- android中intent傳遞list或者對象的方法
- Android Intent的幾種用法詳細(xì)解析
- Android 廣播大全 Intent Action 事件詳解
- Android中使用IntentService創(chuàng)建后臺服務(wù)實例
- Android利用Intent.ACTION_SEND進(jìn)行分享
相關(guān)文章
Android設(shè)置桌面背景圖片的實現(xiàn)方法
有時候我們需要用android設(shè)置桌面背景圖片,這里簡單分享下,方便需要的朋友2013-06-06android中使用Html渲染的方式實現(xiàn)必填項前面的*號示例
本篇文章主要介紹了android中使用Html渲染的方式實現(xiàn)必填項前面的*號示例,具有一定的參考價值,有興趣的可以了解一下2017-09-09Android 多線程實現(xiàn)重復(fù)啟動與停止的服務(wù)
這篇文章主要介紹了Android 多線程實現(xiàn)重復(fù)啟動與停止的服務(wù)的相關(guān)資料,多線程環(huán)境下為了避免死鎖,一般提倡開放調(diào)用,開放調(diào)用可以避免死鎖,它的代價是失去原子性,這里說明重復(fù)啟動與停止的服務(wù),需要的朋友可以參考下2017-08-08Android APP啟動方式、啟動流程及啟動優(yōu)化分析
這篇文章主要介紹了Android APP啟動方式、啟動流程及啟動優(yōu)化分析的相關(guān)資料,需要的朋友可以參考下2016-09-09總結(jié)Android中MD風(fēng)格相關(guān)控件
自Android5.0發(fā)布以來,谷歌推出全新的Material Desigen設(shè)計風(fēng)格,時過一年多了,在國內(nèi)也看到很多應(yīng)用在慢慢適應(yīng)MD設(shè)計風(fēng)格。今天小編給大家總結(jié)下Android中MD風(fēng)格相關(guān)控件的知識,有需要的可以參考學(xué)習(xí)。2016-08-08Android自定義控件之組合控件學(xué)習(xí)筆記分享
這篇文章主要為大家分享了Android自定義控件之組合控件學(xué)習(xí)筆記,具有一定的實用性和參考價值,感興趣的小伙伴們可以參考一下2016-05-05