理解 IntentService 原理
本人只是 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
了,就讓我們用一個小例子來演示一下:
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
,因為這些和本文所講內容並無直接關係。
請求的接收和處理程式碼都已完成,接下來就是傳送請求的程式碼邏輯,如下:
// 傳送“下載”請求 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()
的時候發現最後一次啟動是第三個請求發生的,並不會停止服務;處理完第二個請求後是類似的,只有在處理完第三個請求後,去嘗試停止服務,這時發現最近一次啟動就是它發起的,可以去停止服務了。停止服務時,其onDestroy
會得到呼叫:
@Override public void onDestroy() { mServiceLooper.quit(); } 複製程式碼
在這裡會停止工作執行緒的訊息迴圈,等待執行緒退出。