Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
服務 IntentService 工做線程 stopself MDandroid
Google 爲方便開發者使用,提升開發效率,封裝了 IntentService 和 HandlerThread。HandlerThread 繼承自 Thread,內部封裝了 Looper。git
IntentService 是繼承自 Service 並處理異步請求的一個類,在 IntentService 內有一個工做線程來處理耗時操做,當任務執行完後,IntentService 會自動中止,不須要咱們去手動結束。若是啓動 IntentService 屢次,那麼每個耗時操做會以工做隊列的方式在 IntentService 的 onHandleIntent 回調方法中執行,依次去執行,執行完自動結束。github
同一個IntentService只會新建一個線程,由於其採用的就是相似主線程的機制。express
IntentService 中使用的 Handler、Looper、MessageQueue 機制把消息發送到線程中去執行的,因此屢次啓動 IntentService 不會從新建立新的服務和新的線程,只是把消息加入消息隊列中等待執行,而若是服務中止,會清除消息隊列中的消息,後續的事件得不到執行。安全
IntentService 類
IntentService是Service的基類,可根據須要處理異步請求(表示爲Intents)。客戶端經過Context#startService(Intent)
調用發送請求; 根據須要啓動服務,使用工做線程依次處理每一個Intent,並在工做完成時自行中止。微信
IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through 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.app
這種「工做隊列處理器」模式一般用於從應用程序的主線程卸載任務。IntentService類的存在就是爲了簡化此模式並處理機制。要使用它,請擴展IntentService並實現onHandleIntent(Intent)。 IntentService將接收Intents,啓動一個工做線程,並根據須要中止服務。less
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 onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.異步
全部請求都在一個工做線程上處理 - 它們可能須要運行很長時間(而且不會阻止應用程序的主循環),但一次只能處理一個請求。async
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.
onHandleIntent 方法
在工做線程上調用此方法並請求處理。一次只處理一個Intent,但處理過程發生在獨立於其餘應用程序邏輯運行的工做線程上。所以,若是此代碼須要很長時間,它將阻止對同一個IntentService的其餘請求,但它不會阻止其餘任何內容。處理完全部請求後,IntentService會自行中止,所以您不該該調用 stopSelf。
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 stopSelf.
最佳應用場景:後臺隊列按順序異步下載、下載完畢後自動結束服務!
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) { Log.i("bqt", "開始onHandleIntent"); onHandleIntent((Intent) msg.obj); Log.i("bqt", "結束onHandleIntent,嘗試stopSelf"); stopSelf(msg.arg1);//處理完畢後自動調用stopSelf結束服務,注意若是此時還有未執行完的message則不會結束! } } public IntentService(String name) { super(); mName = name; //Used to name the worker thread, important only for debugging. } public void setIntentRedelivery(boolean enabled) { mRedelivery = enabled; } @Override public void onCreate() { // TODO: It would be nice to have an option to hold a partial wakelock 若是有一個部分喚醒鎖的選項會更好 super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); //You should not override this method for your IntentService. Instead, override onHandleIntent return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; } @Override public void onStart(Intent intent, int startId) { //每啓動一次onStart方法,就會把消息數據發給mServiceHandler,至關於發送了一次Message消息給HandlerThread的消息隊列 //mServiceHandler會把數據傳給onHandleIntent方法,因此每次onStart以後都會調用咱們重寫的onHandleIntent方法去處理數據 Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); } @Override public void onDestroy() { mServiceLooper.quit(); } @Override public IBinder onBind(Intent intent) { return null; //Unless you provide binding for your service, you don't need to implement this method } protected abstract void onHandleIntent(Intent intent); }
public class MyIntentService extends IntentService { public MyIntentService() { super("包青天的工做線程"); } @Override protected void onHandleIntent(Intent intent) {//注意,由於這裏是異步操做,因此這裏不能直接使用Toast。 //全部請求的Intent記錄會按順序加入到【隊列】中並按順序【異步】執行,而且每次只會執行【一個】工做線程 //當全部任務執行完後IntentService會【自動】中止 Log.i("bqt", "onHandleIntent"); try { int count = 0; sendThreadStatus("線程啓動", count); Thread.sleep(500); while (count <= 100) { count++; Thread.sleep(30); sendThreadStatus("線程運行中...", count); } sendThreadStatus("線程結束", count); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void onCreate() { super.onCreate(); Log.i("bqt", "onCreate"); sendServiceStatus("服務啓動"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("bqt", "onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.i("bqt", "onDestroy"); sendServiceStatus("服務結束"); } // 發送服務狀態信息 private void sendServiceStatus(String status) { Intent intent = new Intent(MainActivity.ACTION_TYPE_SERVICE); intent.putExtra("status", status); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } // 發送線程狀態信息 private void sendThreadStatus(String status, int progress) { Intent intent = new Intent(MainActivity.ACTION_TYPE_THREAD); intent.putExtra("status", status); intent.putExtra("progress", progress); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } }
public class MainActivity extends ListActivity { public static final String ACTION_TYPE_SERVICE = "com.bqt.type.service"; public static final String ACTION_TYPE_THREAD = "com.bqt.type.thread"; ProgressBar progressBar; TextView tvServiceStatus; TextView tvThreadStatus; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String[] array = {"啓動 IntentService", "結束 IntentService", "",}; setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array))); tvServiceStatus = new TextView(this); tvThreadStatus = new TextView(this); progressBar = new ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal); progressBar.setIndeterminate(false); progressBar.setMax(100); getListView().addFooterView(tvServiceStatus); getListView().addFooterView(tvThreadStatus); getListView().addFooterView(progressBar); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_TYPE_SERVICE); intentFilter.addAction(ACTION_TYPE_THREAD); LocalBroadcastManager.getInstance(this).registerReceiver(new MyBroadcastReceiver(), intentFilter); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { switch (position) { case 0: startService(new Intent(this, MyIntentService.class));// 像啓動 Service 那樣啓動 IntentService break; case 1: stopService(new Intent(this, MyIntentService.class)); break; case 2: break; } } class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case ACTION_TYPE_SERVICE: tvServiceStatus.setText("服務狀態:" + intent.getStringExtra("status")); break; case ACTION_TYPE_THREAD: int progress = intent.getIntExtra("progress", 0); tvThreadStatus.setText("線程狀態:" + intent.getStringExtra("status")); progressBar.setProgress(progress); break; } } } }
點擊[啓動 IntentService],服務運行,線程計數完成後,服務和線程都結束。
15:18:45.488: onCreate 15:18:45.489: onStartCommand 15:18:45.489: onStart 15:18:45.489: 開始onHandleIntent 15:18:45.489: onHandleIntent //【開始在工做線程執行任務】 15:18:49.651: 結束onHandleIntent,嘗試stopSelf //【線程執行完畢後調用stopSelf結束任務】 15:18:49.659: onDestroy //【服務成功被結束】
點擊[啓動 IntentService],服務運行,線程計數完成前,點擊[中止 IntentService],服務結束,線程計數完成後線程結束。
15:43:17.569: onCreate 15:43:17.570: onStartCommand 15:43:17.570: onStart 15:43:17.570: 開始onHandleIntent 15:43:17.570: onHandleIntent 15:43:18.895: onDestroy //【調用stopService會當即結束服務,但工做線程並不會結束,而會正常的執行】 15:43:21.724: 結束onHandleIntent,嘗試stopSelf //【線程正常執行完畢,此前服務已經結束了】
點擊兩次[啓動 IntentService],服務運行,第一次線程計數完成後,進行第二次線程計數,兩次完成後,服務和線程都結束。
14:36:30.794: onCreate 14:36:30.795: onStartCommand 14:36:30.795: 開始onHandleIntent 14:36:30.796: onHandleIntent 14:36:32.218: onStartCommand //【在執行第一個任務時啓動第二個任務】 14:36:34.986: 結束onHandleIntent,嘗試stopSelf //【由於此startId不是最新的startId,因此服務結束失敗】 ----------------------------------------- 14:36:34.993: 開始onHandleIntent 14:36:34.993: onHandleIntent 14:36:39.178: 結束onHandleIntent,嘗試stopSelf //【第二個任務完成時的startId是最新的startId,因此能成功結束服務】 ----------------------------------------- 14:36:39.188: onDestroy //【服務結束】
點擊兩次[啓動 IntentService],服務運行,在第一次線程計數完成前,點擊[中止 IntentService],服務結束,第一次線程計數結束後不進行第二次計數。
16:34:05.052: onCreate 16:34:05.054: onStartCommand 16:34:05.054: onStart 16:34:05.055: 開始onHandleIntent 16:34:05.055: onHandleIntent 16:34:05.463: onStartCommand 16:34:05.463: onStart 16:34:05.985: onDestroy //【調用stopService會當即結束服務,但工做線程並不會結束,而會正常的執行】 16:34:09.237: 結束onHandleIntent,嘗試stopSelf //【由於服務已經結束,因此不會再執行未開啓的任務】
結論
stopSelf()
:直接中止服務,和調用 Context#stopService
的效果徹底一致。stopSelf(int startId)
:在其參數 startId 跟最後啓動該 service 時生成的 ID 相等時纔會執行中止服務。stopSelfResult(int startId)
:和 stopSelf(int startId)
的區別僅僅是有返回值,當成功中止服務時返回 true,不然返回 false。stopSelf(int startId)
結束本身時,老是拿最新的 startId 和上一次傳入的 startId 做比較,若是相等則結束本身,若是不相等則不結束,而IntentService 中維護着一個消息隊列,要想結束本身必須等到消息隊列沒有消息了。使用場景
若是同時有多個服務啓動請求發送到onStartCommand()
,不該該在處理完一個請求後調用stopSelf(),由於在調用此函數銷燬service以前,可能 service 又接收到新的啓動請求,若是此時 service 被銷燬,新的請求將得不處處理。此狀況應該調用stopSelf(int startId)
,例如 IntentService 中,在執行完異步任務後,就是經過調用stopSelf(int startId)
來結束服務的。
源碼
先看看 onStartCommand
方法。
每次經過 startService 方式啓動服務時,都會調用 onStartCommand 方法,此方法中有一個 startId,這個 startId 表明啓動服務的次數,也表明着某一特定啓動請求。
文檔中明確說明了,這個 startId 其實就是在 stopSelfResult(int) 中用的。
//startId:A unique integer representing this specific request to start. Use with stopSelfResult(int). public int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) { onStart(intent, startId); return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY; }
再看看 stopSelf()
方法,能夠看到其只是調用了stopSelf(-1)
。
//Stop the service, if it was previously先前 started. //This is the same as calling Context#stopService for this particular特定 service. public final void stopSelf() { stopSelf(-1); }
再看看 stopSelf(int startId)
方法,文檔中沒有什麼解釋,只說是舊版本中stopSelfResult
的無返回值形式。
//Old version of stopSelfResult that doesn't return a result. public final void stopSelf(int startId) { if (mActivityManager == null) return; try { mActivityManager.stopServiceToken(new ComponentName(this, mClassName), mToken, startId); } catch (RemoteException ex) { } }
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; //中止失敗 }
如下是文檔中的詳細描述:
若是最近啓動服務時的ID就是此startId,則中止服務。
Stop the service if the most recent time it was started was startId.
這與對此特定服務調用Context#stopService的效果相同,但若是有一個來自客戶端的啓動請求(在執行此方法時這個請求還在onStart中,因此你沒發現這個請求),則容許您安全地避免中止服務。
This is the same as calling 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 #onStart.
要當心調用此函數後的順序。
Be careful about ordering of your calls to this function.
若是在爲以前收到的ID調用以前使用最近收到的ID調用此函數,則不管如何都會當即中止該服務。
If you call this function with the most-recently received ID before you have called it for previously received IDs, the service will be immediately stopped anyway.
若是您最終可能無序處理ID(例如經過在不一樣的線程上調度它們),那麼您有責任按照收到它們的順序中止它們。
If you may end up processing IDs out of order (such as by dispatching them on separate threads), then you are responsible for stopping them in the same order you received them.
參數與返回值:
2019-2-13