一、IntentService簡介
我們看下官方對其的解釋:
/**
* IntentService is a base class for {@link Service}s that handle asynchronous
* requests (expressed as {@link Intent}s) on demand. Clients send requests
* through {@link android.content.Context#startService(Intent)} calls; the
* service is started as needed, handles each Intent in turn using a worker
* thread, and stops itself when it runs out of work.
*
* <p>This "work queue processor" pattern is commonly used to offload tasks
* from an application's main thread. The IntentService class exists to
* simplify this pattern and take care of the mechanics. To use it, extend
* IntentService and implement {@link #onHandleIntent(Intent)}. IntentService
* will receive the Intents, launch a worker thread, and stop the service as
* appropriate.
*
* <p>All requests are handled on a single worker thread -- they may take as
* long as necessary (and will not block the application's main loop), but
* only one request will be processed at a time.
*/
總結下來就是:
- IntentService是Service類的子類,用來處理異步請求;
- 客戶端可以通過startService(Intent)方法傳遞請求給IntentService;
- IntentService單獨開啓了一個線程來處理所有的Intent請求所對應的任務,以免事務處理阻塞主線程,而且任務是按先後順序逐個進行處理的;
- 當IntentService處理完所有的任務後,它會在適當的時候自動結束服務。
二、IntentService使用步驟
- 繼承IntentService,實現構造方法和onHandleIntent()方法;
- 在Manifest文件中註冊自己的IntentService類;
- 創建任務請求併發送到IntentService進行處理;
- 通過IntentService向其它組件發送任務處理的結果;
- 在接收的組件中進行後續處理。
三、IntentService使用實例
下面是一個模擬圖片上傳的demo。
ImgUploadService.java類:
public class ImgUploadService extends IntentService {
public static final String TAG = "ImgUploadService";
private static final String ACTION_UPLOAD_IMG = "com.demo.service.action.UPLOAD_IMAGE";
public static final String EXTRA_IMG_PATH = "com.demo.service.extra.IMG_PATH";
public ImgUploadService(String name) {
super(name);
Log.d(TAG, "ImgUploadService[" + " ThreadName: " + name + " ]");
}
@Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent");
if (intent != null) {
final String action = intent.getAction();
if (ACTION_UPLOAD_IMG.equals(action)) {
final String path = intent.getStringExtra(EXTRA_IMG_PATH);
handleUploadImg(path);
}
}
}
private void handleUploadImg(String path) {
try {
Thread.sleep(3000); //模擬上傳耗時
Intent intent = new Intent(IntentServiceActivity.UPLOAD_RESULT);
intent.putExtra(EXTRA_IMG_PATH, path);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void startUploadImg(Context context, String path) {
Intent intent = new Intent(context, ImgUploadService.class);
intent.setAction(ACTION_UPLOAD_IMG);
intent.putExtra(EXTRA_IMG_PATH, path);
context.startService(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
}
IntentServiceActivity.java類:
public class IntentServiceActivity extends Activity {
public static final String UPLOAD_RESULT = "com.demo.service.UPLOAD_RESULT";
private LinearLayout mLlContainer;
private TextView mBtnUpload;
int i = 0;
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
setContentView(R.layout.activity_main_handlerthread);
mLlContainer = (LinearLayout) findViewById(R.id.ll_container);
mBtnUpload = (TextView) findViewById(R.id.btn_upload);
registerReceiver();
mBtnUpload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addTask(); //模擬上傳
}
});
}
private void registerReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(UPLOAD_RESULT);
LocalBroadcastManager.getInstance(this).registerReceiver(mUploadImgReceiver, filter);
}
public void addTask() {
String path = "圖片" + i++ + ".png";
ImgUploadService.startUploadImg(this, path);
TextView tv = new TextView(this);
mLlContainer.addView(tv);
tv.setText(path + " ....正在上傳中....");
tv.setTag(path);
}
private void handleResult(String path) {
TextView tv = (TextView) mLlContainer.findViewWithTag(path);
tv.setText(path + " ----上傳成功---- ");
}
private BroadcastReceiver mUploadImgReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(UPLOAD_RESULT)) {
String path = intent.getStringExtra(ImgUploadService.EXTRA_IMG_PATH);
handleResult(path);
}
}
};
@Override
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mUploadImgReceiver);
}
}
activity_main.xml文件:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_upload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
android:text="發送上傳請求"/>
</LinearLayout>
四、IntentService源碼解析
IntentService本身就是一個Service,它擁有Service的所有生命週期方法,但內部是通過HandlerThread實現異步執行任務的,HandlerThread是一個內部維護了一個消息隊列的線程,對於這一點,可查看博客:Android多線程開發之HandlerThread的使用。
既然IntentService有生命週期,那麼就從它的構造函數看起:
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public IntentService(String name) {
super();
mName = name;
}
構造方法很簡單,用於創建一個IntentService對象,參數namne用於定義工作線程的名字,僅用於調試作用。接下來看下IntentService的onCreate()方法:
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
// 創建一個HandlerThread對象,並傳入工作線程的名字
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
// 開啓後臺工作線程
thread.start();
// 獲取後臺工作線程的Looper對象
mServiceLooper = thread.getLooper();
// 創建一個ServiceHandler對象,用來處理異步消息。
mServiceHandler = new ServiceHandler(mServiceLooper);
}
在onCreate()方法裏,首先利用HandlerThread類創建了一個循環的工作線程,接着獲取工作線程中的Looper對象並將其作爲參數創建了一個叫ServiceHandler的類,該類是IntentService的內部類,該類繼承了Handler,我們看它的定義:
public abstract class IntentService extends Service {
//volatile關鍵字保證變量每次在使用的時候,都從主存中取。而不是從各個線程的“工作內存”中讀取
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) {
//一收到消息就回調到onHandleIntent()中進行處理
onHandleIntent((Intent)msg.obj);
//處理完消息就調用stopSelf()方法,並傳入消息的索引值
stopSelf(msg.arg1);
}
}
}
從源碼中可以看到,mServiceLooper和mServiceHandler都加了volatile關鍵字修飾,這是爲了保證變量在每次使用的時候都從主內存中讀取,而不是從各個線程的“工作內存”中讀取,也就是說保證內存的可見性。
接着看onHandleIntent()方法,該方法是在ServiceHandler收到消息後回調的,也即onHandleIntent()方法是在HandlerThread工作線程中執行的,它是一個抽象方法,留給調用者去實現耗時任務的。
/**
* This method is invoked on the worker thread with a request to process.
* Only one Intent is processed at a time, but the processing happens on a
* worker thread that runs independently from other application logic.
* So, if this code takes a long time, it will hold up other requests to
* the same IntentService, but it will not hold up anything else.
* When all requests have been handled, the IntentService stops itself,
* so you should not call {@link #stopSelf}.
*
* @param intent The value passed to {@link
* android.content.Context#startService(Intent)}.
*/
protected abstract void onHandleIntent(Intent intent);
此外,因爲任務是通過ServiceHandler發送給異步線程的消息隊列來處理的,而消息隊列裏的消息是依次取出並執行的,所以從這裏可以瞭解到任務必定是串行執行的。而執行完一個消息後,調用了一個帶有startId參數的stopSelf()方法:
** * Old version of {@link #stopSelfResult} that doesn't return a result. * * @see #stopSelfResult */
public final void stopSelf(int startId) {
if (mActivityManager == null) {
return;
}
try {
mActivityManager.stopServiceToken(
new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
}
該方法實際上是通過調用ActivityManager的stopServiceToken()方法來停止當前服務的,不過服務不會馬上停止,而是等待繼續完成剩下的任務後才自動結束,因爲方法參數裏攜帶了一個startId,它可以看做是一個請求的唯一標識,只有當所有的請求都結束,我們的Service才自行銷燬,而startId是從onStartCommand()裏被調用的,我們看下它的源碼:
/**
* You should not override this method for your IntentService. Instead,
* override {@link #onHandleIntent}, which the system calls when the IntentService
* receives a start request.
* @see android.app.Service#onStartCommand
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
對於Service,每次調用startServcie方法啓動一個服務,都會調用一次onStartCommand()方法,每次也會帶一個startId過去,如果執行多個任務,那麼就會存在多個startId,然後在onStart()方法中通過mServiceHandler獲得一個消息對象msg,然後將startId作爲該消息的消息碼,將異步任務請求intent作爲消息內容封裝成一個消息msg發送到mServiceHandler中進行異步處理,最後回調到onHandleIntent()中,子類通過實現onHandleIntent()即可進行異步耗時任務的執行。
這裏注意,我們不需要自己去重寫onStartCommand()和onStart()方法,但onHandleIntent()必須重寫。
當所有的startId都處理完後,IntentService會調用onDestroy()自行銷燬,消息隊列也會自行退出。
@Override
public void onDestroy() {
mServiceLooper.quit();
}
這裏還有兩個方法,留給子類必要的時候去實現它們,分別是setIntentRedelivery()和onBind()方法:
/**
* Sets intent redelivery preferences. Usually called from the constructor
* with your preferred semantics.
*
* <p>If enabled is true,
* {@link #onStartCommand(Intent, int, int)} will return
* {@link Service#START_REDELIVER_INTENT}, so if this process dies before
* {@link #onHandleIntent(Intent)} returns, the process will be restarted
* and the intent redelivered. If multiple Intents have been sent, only
* the most recent one is guaranteed to be redelivered.
*
* <p>If enabled is false (the default),
* {@link #onStartCommand(Intent, int, int)} will return
* {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
* dies along with it.
*/
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
/**
* Unless you provide binding for your service, you don't need to implement this
* method, because the default implementation returns null.
* @see android.app.Service#onBind
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
setIntentRedelivery()表示設置是否重新派發,通常在構造方法中進行設置。如果設置爲true,onStartCommand()將會返回START_REDELIVER_INTENT,如果在onHandleIntent()返回之前進程已經死掉了,那麼進程將會重新啓動,intent會重新發送,如果有大量的intent發送了,那麼只會保證最近的intent會被重新派發。如果設置爲false(也即默認的值),那麼onStartCommand()將會返回START_NOT_STICKY,這時如果進程死了,Intent也會停止傳遞。
對於onBind()方法,默認返回了null,除非我們是通過bindService()啓動的服務,否則我們也不需要實現它。
五、IntentService應用場景
從前面的分析可以知道IntentService有以下特點:
- IntentService繼承自Service,任務的執行都是以隊列的形式進行的,而且不受UI生命週期的影響;
- 創建了一個獨立的工作線程來處理onStartCommand()發送過來的任務,並逐個排隊進行處理;
- 不需要自己主動啓動和關閉線程;
- 不需要主動調用stopSelf()來結束服務,任務處理完後會自動進行關閉;
但也有以下侷限性:
- 不能直接和UI進行交互,需自行處理線程之間的通信;
- 工作任務是順序執行的,一旦隊列中有某個任務執行時間過長,那麼就會導致後續的任務都會被延遲處理;
- 正在執行的任務無法被打斷;
所以IntentService只適合處理簡單的順序執行的後臺耗時任務,不適合處理高併發的後臺耗時任務。比如多文件下載,多圖片上傳等都可用IntentService進行處理,處理完後若需要進行通知,可通過廣播或者其它一些線程切換工具進行異步通知,比如EventBus等。