1. 程式人生 > >[Android][Service簡介]

[Android][Service簡介]

1.什麼是Service

Service 是一個可以在後臺執行長時間執行操作而不提供使用者介面的應用元件。服務可由其他應用元件啟動,而且即使使用者切換到其他應用,服務仍將在後臺繼續執行。 此外,元件可以繫結到服務,以與之進行互動,甚至是執行程序間通訊 (IPC)。上面的這句話是官方的解釋,這句化的第一句很明顯的闡述了Service的使用場景,也就是說在不需要與使用者進行互動(使用者點選螢幕等觸發事件的行為都是互動行為)的情景,以及某個操作需要在後臺長時間執行的情況。只要滿足這兩個情況之一,都可以考慮使用Service。
此外,必須注意的是Service必須要被其他元件(Activity等元件)啟動才能執行。

Intent mIntent = new Intent(getApplicationContext(), AboutService.class);
getApplicationContext().startService(mIntent);

但是,我測試通過ApplicationContext啟動服務,它也確實能夠啟動,這裡冒昧下個結論,只要有Context的地方,都可以通過Context啟動服務。
服務在其託管程序的主執行緒中執行,它既不建立自己的執行緒,也不在單獨的程序中執行(除非另行指定)。 這意味著,如果服務將執行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或聯網)(可以理解為耗時的操作),則應在服務內建立新執行緒來完成這項工作。通過使用單獨的執行緒,可以降低發生“應用無響應”(ANR) 錯誤的風險,而應用的主執行緒仍可繼續專注於執行使用者與 Activity 之間的互動。其實這句話可以簡單的理解為服務是在主執行緒中執行,因此如果服務中有耗時任務應該放到子執行緒中去執行。

2.Service按啟動方式分類

類別 區別 注意
startService 當應用元件(如 Activity)通過呼叫 startService() 啟動服務時,服務即處於“啟動”狀態。一旦啟動,服務即可在後臺無限期執行,即使啟動服務的元件已被銷燬也不受影響。 已啟動的服務通常是執行單一操作,而且不會將結果返回給呼叫方。例如,它可能通過網路下載或上傳檔案。 操作完成後,服務會自行停止執行。 因為這種方式啟動的服務一旦啟動即可在後臺無限期執行,即使啟動服務的元件被銷燬了也不會影響這個服務,所以為了不引起記憶體溢位等問題,不要忘了在不需要該服務的時候要記得停止該服務。服務可以使用 stopSelf() 自行停止執行,或由其他元件通過呼叫 stopService() 停止。
2.通過startService啟動的服務是無法與其他元件進行互動的。
3.通過startService啟動的服務,是有可能被系統殺死的。僅當記憶體過低且必須回收系統資源以供具有使用者焦點的 Activity 使用時,Android 系統才會強制停止服務。
4.如果服務已啟動並要長時間執行,則系統會隨著時間的推移降低服務在後臺任務列表中的位置,而服務也將隨之變得非常容易被終止;如果服務是啟動服務,則您必須將其設計為能夠妥善處理系統對它的重啟。 如果系統終止服務,那麼一旦資源變得再次可用,系統便會重啟服務(不過這還取決於從 onStartCommand() 返回的值)。
bindService 當應用元件通過呼叫 bindService() 繫結到服務時,服務即處於“繫結”狀態。繫結服務提供了一個客戶端-伺服器介面,允許元件與服務進行互動、傳送請求、獲取結果,甚至是利用程序間通訊 (IPC) 跨程序執行這些操作。 僅當與另一個應用元件繫結時,繫結服務才會執行。 多個元件可以同時繫結到該服務,但全部取消繫結後,該服務即會被銷燬。 1.僅當與另一個應用元件繫結時,繫結服務才會執行.也就是說通過bindService啟動服務必須要繫結一個應用元件才能執行。而且當該被繫結的元件銷燬的時候,該服務也會被銷燬。
2.通過bindService方式啟動的服務是可以與其他元件進行互動的。如需與 Activity 和其他應用元件中的服務進行互動,或者需要通過程序間通訊 (IPC) 向其他應用公開某些應用功能,則應建立繫結服務。
3.僅當記憶體過低且必須回收系統資源以供具有使用者焦點的 Activity 使用時,Android 系統才會強制停止服務。如果將服務繫結到具有使用者焦點的 Activity,則它不太可能會終止;如果將服務宣告為在前臺執行,則它幾乎永遠不會終止。
4.從 Android 5.0(API 級別 21)開始,如果使用隱式 Intent 呼叫 bindService(),系統會引發異常。這是官方的原話,但是經過測試不管是bindService()還是startService(),只要是隱式啟動的Intent沒有呼叫setPackage設定app的包名,都會報錯。
5.多個客戶端可同時繫結到一個服務。不過,只有在第一個客戶端繫結時,系統才會呼叫服務的 onBind()方法來檢索 IBinder。系統隨後無需再次呼叫 onBind(),便可將同一 IBinder 傳遞至任何其他繫結的客戶端。

為了確保應用的安全性,請始終使用顯式 Intent 啟動或繫結 Service,且不要為服務宣告 Intent 過濾器。啟動哪個服務存在一定的不確定性,而如果對這種不確定性的考量非常有必要,則可為服務提供 Intent 過濾器並從 Intent 中排除相應的元件名稱,但隨後必須使用 setPackage() 方法設定 Intent 的軟體包(setPackage設定的是app的包名,而不是該服務的路徑),這樣可以充分消除目標服務的不確定性。這句話的意思是為了確保安全,要使用顯示Intent的方法來啟動或者繫結服務,而且一般不要為服務宣告Intent過濾器。(一般隱式啟動服務是需要使用到Intent過濾器設定action)使用隱式 Intent 啟動服務存在安全隱患(如果未設定android:exported=”false”那麼其他應用可以呼叫該應用的服務,這確實不安全),因為無法確定哪些服務將響應 Intent,且使用者無法看到哪些服務已啟動(這裡在filter裡的例子測試了一下,在AndroidManifest將兩個服務的過濾器裡的action設定為相同的,多次測試結果是隻有先設定過濾的服務會被啟動,而另一個服務未被啟動。但是這句話的意思是不能確定哪個服務被啟動,所以不清楚是不是先設定過濾的服務一定會被啟動,不清楚是不是設定了相同Action的服務只能有一個被啟動)。隱式啟動一點要設定setPackage() 否則會報錯。

        //通過ApplicationContext來啟動服務
//        IllegalArgumentException: Service Intent must be explicit
//        經過查詢相關資料,發現是因為Android5.0中service的intent一定要顯性宣告,當這樣繫結的時候不會報錯。
//  隱式啟動
//        Intent mIntent = new Intent();
//        mIntent.setAction("xxx");
//  startService(mIntent);
        //顯示啟動
        Intent mIntent = new Intent(this, AboutService.class);
        startService(mIntent);

3.服務的生命週期

服務生命週期。左圖顯示了使用 startService() 所建立的服務的生命週期,右圖顯示了使用 bindService() 所建立的服務的生命週期。

服務生命週期(從建立到銷燬)可以遵循兩條不同的路徑:
• 啟動服務
該服務在其他元件呼叫 startService() 時建立,然後無限期執行,且必須通過呼叫 stopSelf() 來自行停止執行。此外,其他元件也可以通過呼叫stopService() 來停止服務。服務停止後,系統會將其銷燬。
• 繫結服務
該服務在另一個元件(客戶端)呼叫 bindService() 時建立。然後,客戶端通過 IBinder 介面與服務進行通訊。客戶端可以通過呼叫unbindService() 關閉連線。多個客戶端可以繫結到相同服務,而且當所有繫結全部取消後,系統即會銷燬該服務。 (服務不必自行停止執行。)
這兩條路徑並非完全獨立。也就是說,您可以繫結到已經使用 startService() 啟動的服務。例如,可以通過使用 Intent(標識要播放的音樂)呼叫 startService() 來啟動後臺音樂服務。隨後,可能在使用者需要稍加控制播放器或獲取有關當前播放歌曲的資訊時,Activity 可以通過呼叫 bindService()繫結到服務。在這種情況下,除非所有客戶端均取消繫結,否則 stopService() 或 stopSelf() 不會實際停止服務。

允許繫結的已啟動服務的生命週期

當一個服務既bindService又startService的時候(先bindService還是先startService並不會有什麼影響,),如果這時候所有的客戶端都呼叫unbindService(),那麼服務會呼叫onUnbind()方法,但是服務還是會繼續執行,只有當呼叫stopService()的時候,服務才會呼叫onDestroy()方法,進入死亡狀態。
當一個服務既bindService又startService的時候(先bindService還是先startService並不會有什麼影響),如果先呼叫stopService(),這時候服務還是會繼續執行,只有當所以客戶端都呼叫unbindService(),這時候服務就會先呼叫onUnbind(),然後呼叫onDestroy()方法,進入死亡狀態。
上面的圖我認為是有錯誤的,應該分兩種情況,也就是上面描述的兩種情況,是先解綁所以服務還是先呼叫stopService。

image.png

4.前臺服務

前臺服務被認為是使用者主動意識到的一種服務,因此在記憶體不足時,系統也不會考慮將其終止。 前臺服務必須為狀態列提供通知,放在“正在進行”標題下方,這意味著除非服務停止或從前臺移除,否則不能清除通知。
例如,應該將通過服務播放音樂的音樂播放器設定為在前臺執行,這是因為使用者明確意識到其操作。 狀態列中的通知可能表示正在播放的歌曲,並允許使用者啟動 Activity 來與音樂播放器進行互動。
要請求讓服務運行於前臺,請呼叫 startForeground()。要從前臺移除服務,請呼叫 stopForeground()。stopForeground此方法採用一個布林值,指示是否也移除狀態列通知。 此方法不會停止服務。 但是,如果您在服務正在前臺執行時將其停止,則通知也會被移除。

image.png

具體例子請檢視com.tan.lgy.testservice.ForegroundService

5.原始碼地址

6.參考文章