本人只是 Android小菜一個,寫技術文件只是為了總結自己在最近學習到的知識,從來不敢為人師,如果裡面有些不正確的地方請大家盡情指出,謝謝!
1.概述
service
的作用相信大家都是非常熟悉的,主要用來在後臺進行任務處理,例如後臺播放音樂、下載檔案、上傳檔案等等。由於service
是執行在主執行緒中的,也有一定的時間限制,如果在主執行緒中對一個任務的處理時間超過了限制,程式就會出現“應用不響應”,即ANR, Application Not Responding
。為了避免這樣情況,都會在service
裡用新的thread
處理一些可能需要更多處理時間的任務。
其實Android
早就替我們設計了一種更方便的service + thread
模式,就是本文要講的IntentService
,通過它可以很方便地實現在service
中使用thread
進行耗時任務的處理。
本文將首先給大家演示下它的基本使用方式,再講解下IntentService
的內部原理。
2. IntentService 的使用
在知道如何使用前,先看看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.
*/
public abstract class IntentService extends Service { ... }
複製程式碼
相信大家都能很容易看懂這段宣告的意思,小菜在這裡簡單為大家總結下,這麼一大段文字主要是說明了兩個問題:
IntentService
是什麼:用來進行處理非同步請求的服務,其內部有一個工作執行緒,所有傳送給服務的請求都會在這個工作執行緒中按序執行,在處理完所有請求後服務會自動停止。IntentService
如何使用:擴充IntentService
並在其擴充類或者叫子類中實現onHandleIntent(Intent)
介面,在這個介面中進行實際的請求處理,這些請求通過Context.startService(Intent)
來進行傳送。
Android SDK
真的可以作為所有SDK
的典範,它會清楚地告訴你“是什麼”和“怎麼用”的問題,針對相對複雜的情況,還會直接在宣告裡給出範例。
既然我們已經知道要如何使用IntentService
了,就讓我們用一個小例子來演示一下:
2.1 服務端
服務端指的是IntentService
端,其作用是接收客戶端傳送過來的請求並處理。
public class TestIntentService extends IntentService {
private static final String TAG = "TestIntentService";
private static final String DEFAULT_NAME = "default_name";
// 為了區分不同的請求和方便呼叫端使用,直接定義了不同的 ACTION.
public static final String DOWNLOAD_ACTION = "com.test.intent.action.DOWNLOAD";
public static final String UPLOAD_ACTION = "com.test.intent.action.UOLOAD";
// 要在 AndroidManifest.xml 裡宣告 servcie,必須提供一個無參建構函式.
public TestIntentService() {
// IntentService 的建構函式需要提供一個工作執行緒的名字資訊.
super(DEFAULT_NAME);
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy");
}
@Override
public void onHandleIntent(Intent intent) {
String action = intent.getAction();
// 根據不同的請求型別進行不同的處理,這裡只是休眠一段時間,並沒有進行實際的處理。
if (DOWNLOAD_ACTION.equals(action)) {
try {
Log.i(TAG, "onHandleIntent, start to download");
Thread.sleep(30 * 1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
} else if (UPLOAD_ACTION.equals(action)) {
try {
Log.i(TAG, "onHandleIntent, start to upload");
Thread.sleep(40 * 1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
}
複製程式碼
在這段程式碼裡,請求處理函式onHandleIntent(Intent)
會根據接收到的請求進行不同的處理,如果收到的是“下載”請求就休眠30秒模擬下載過程,如果收到的是“上傳”請求就休眠40秒模擬上傳過程。
在寫好了service
邏輯後一定不要忘記在AndroidManifest.xml
對其進行註冊,否則是無法使用的,註冊程式碼如下:
<service android:name=".TestIntentService" />
複製程式碼
這裡只是簡單地對其進行註冊,並沒有設定其他相關屬性,例如
intent-filter
,因為這些和本文所講內容並無直接關係。
2.2 客戶端
客戶端主要是用來向服務端傳送請求。
// 傳送“下載”請求
Intent downloadIntent = new Intent(this, TestIntentService.class);
downloadIntent.setAction(TestIntentService.DOWNLOAD_ACTION);
startService(downloadIntent);
// 傳送“上傳”請求
Intent upIntent = new Intent(this, TestIntentService.class);
upIntent.setAction(TestIntentService.UPLOAD_ACTION);
startService(upIntent);
複製程式碼
現在看當傳送這“下載”和“上傳”請求後,IntentService
是如何響應的:
02-27 12:58:23.100 24190 24190 I TestIntentService: onCreate
02-27 12:58:23.102 24190 24240 I TestIntentService: onHandleIntent, start to download
02-27 12:58:53.107 24190 24240 I TestIntentService: onHandleIntent, start to upload
02-27 12:59:33.115 24190 24190 I TestIntentService: onDestroy
複製程式碼
可以看到:在傳送“下載”請求的時候,service
首先被建立,然後開始處理這個“下載請求”,僅接著“上傳”請求也被接收並在處理完第一個請求後開始處理,在處理完所有請求後service
被自動銷燬。
3. IntentService 的原理
前面已經講了如何通過IntentService
實現在工作執行緒中處理較耗時任務,那麼IntentService
內部又是如何實現的呢?本節我們通過分析它的原始碼來一探究竟。
3.1 建立工作執行緒
既然IntentService
的功能是在工作執行緒中處理任務,首先來看看這個工作執行緒是如何建立出來的。
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 thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
// 和工作執行緒內部的訊息迴圈關聯
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
複製程式碼
當IntentService
第一次啟動的時候會呼叫其onCreate
來完成一些初始化操作:
- 首先建立了一個
HandlerThread
物件,這就是前面一直提到的“工作執行緒”。大家對Handler
和Thread
都很瞭解,那這個HandlerThread
是什麼呢?簡單來說,它就是內部有一個訊息迴圈佇列的執行緒,我們知道預設的執行緒內部是沒有訊息迴圈佇列的,這就導致我們無法直接在其內部使用Handler
。Android
為了方便使用,直接提供了一個含有訊息迴圈佇列的HandlerThread
。 - 利用已建立的
HandlerThread
內部的訊息迴圈建立一個ServiceHandler
物件,這樣它的訊息處理函式handleMessage
就會在對應的執行緒中執行了。
3.2 接收和處理請求
既然工作執行緒已經建立完成,這時就要考慮如何接收和處理客戶端傳送過來的請求了,已經瞭解到客戶端是通過startService
來傳送請求的,結合service
的生命週期,會執行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(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
複製程式碼
從這段程式碼看到,onStartCommand
會直接呼叫onStart
,在這裡對傳送過來的請求接收並通過mServiceHandler
進行處理。
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
複製程式碼
在handleMessage
中對接收到的請求用onHandleIntent
進行實際處理,而onHandleIntent
就是我們在使用過程中必須實現的處理邏輯。
3.3 銷燬工作執行緒
前面提到:當所有請求都被處理完成後,service
就會被銷燬,這是如何實現的呢?在上面看到handleMessage
方法裡在處理完當前請求時會呼叫stopSelf(msg.arg1)
來嘗試停止當前服務,之所以說“嘗試”,是因為它不一定能真正停止服務。還是來看下stopSelf(int)
的實現程式碼:
/**
* 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) {
}
}
/**
* Stop the service if the most recent time it was started was
* <var>startId</var>. This is the same as calling {@link
* android.content.Context#stopService} for this particular service but allows you to
* safely avoid stopping if there is a start request from a client that you
* haven't yet seen in {@link #onStart}.
*/
public final boolean stopSelfResult(int startId) {
if (mActivityManager == null) {
return false;
}
try {
return mActivityManager.stopServiceToken(
new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
return false;
}
複製程式碼
在stopSelf(int)
的宣告裡提到它是stopSelfResult(int)
的老版本,唯一的區別就是沒有返回值。那我們直接看stopSelfResult(int)
的宣告,其中提到只有在當前的service
的最近一次啟動是startId
發起的才會被停止。我們把這句話放在IntentService
的場景裡去理解,如果說當前接收到3個請求,在處理第一個請求後打算去停止服務,但是呼叫stopSelf(int)
的時候發現最後一次啟動是第三個請求發生的,並不會停止服務;處理完第二個請求後是類似的,只有在處理完第三個請求後,去嘗試停止服務,這時發現最近一次啟動就是它發起的,可以去停止服務了。
停止服務時,其onDestroy
會得到呼叫:
@Override
public void onDestroy() {
mServiceLooper.quit();
}
複製程式碼
在這裡會停止工作執行緒的訊息迴圈,等待執行緒退出。
4. 總結
IntentService
能夠接受使用者傳送的請求並在工作執行緒中順序處理,處理完成後自動退出,但是由於從 Android O
開始對後臺服務增加了更嚴格的控制,導致當前程式在後臺時其含有的後臺服務也無法長期存活,IntentService
的使用也有了一定的限制,推薦使用更好的JobIntentService
,感興趣的同學可以自己去研究。