Android Service最全面的解析

風靈使發表於2018-11-16

ServiceAndroid中一個類,它是Android四大元件之一,使用Service可以在後臺執行長時間的操作( perform long-running operations in the background ),Service並不與使用者產生UI互動。其他的應用元件可以啟動Service,即便使用者切換了其他應用,啟動的Service仍可在後臺執行。一個元件可以與Service繫結並與之互動,甚至是跨程式通訊(IPC)。例如,一個Service可以在後臺執行網路請求、播放音樂、執行檔案讀寫操作或者與 content provider互動 等。

本文將介紹Services的定義、建立、啟動、繫結、前臺Service等相關內容,如需訪問官方原文,您可以點選這個連結:《Services》

Services

Services有兩種啟動形式:

  • Started:其他元件呼叫startService()方法啟動一個Service。一旦啟動,Service將一直執行在後臺(run in the background indefinitely)即便啟動Service的元件已被destroy。通常,一個被start的Service會在後臺執行單獨的操作,也並不給啟動它的元件返回結果。比如說,一個startService執行在後臺下載或上傳一個檔案的操作,完成之後,Service應自己停止。
  • Bound:其他元件呼叫bindService()方法繫結一個Service。通過繫結方式啟動的Service是一個client-server結構,該Service可以與繫結它的元件進行互動。一個bound service僅在有元件與其繫結時才會執行(A bound service runs only as long as another application component is bound to it),多個元件可與一個service繫結,service不再與任何元件繫結時,該service會被destroy

當然,service也可以同時在上述兩種方式下執行。這涉及到Service中兩個回撥方法的執行:onStartCommand()(通過start方式啟動一個service時回撥的方法)、onBind()(通過bind方式啟動一個service回撥的方法)。

無論通過那種方式啟動service(start、bind、start&bind),任何元件(甚至其他應用的元件)都可以使用service。並通過Intent傳遞引數。當然,您也可以將Servicemanifest檔案中配置成私有的,不允許其他應用訪問。

!請注意:Service執行在主執行緒中(A service runs in the main thread of its hosting process),Service並不是一個新的執行緒,也不是新的程式。也就是說,若您需要在Service中執行較為耗時的操作(如播放音樂、執行網路請求等),需要在Service中建立一個新的執行緒。這可以防止ANR的發生,同時主執行緒可以執行正常的UI操作。

使用Service還是使用Thread?

Service是一個執行在後臺的元件,並不與使用者互動。您僅在需要的時候建立Servicecreate a service only if that is what you need)。
當使用者正在與UI互動時,需要執行一些主執行緒無法完成的工作,應當建立一個執行緒。例如當activity正在執行時,需要播放音樂,此時需要在ActivityonCreate()中建立執行緒並在onStart()中開啟。最後在onStop()中停止。您也可以考慮使用AsyncTaskHandlerThread來替代Thread建立執行緒。

Service基礎(The Basics)

為了建立Service,需要繼承Service類。並重寫它的回撥方法,這些回撥方法反應了Service的生命週期,並提供了繫結Service的機制。最重要的Service的生命週期回撥方法如下所示:

  • onStartCommand():當其他元件呼叫startService()方法請求啟動Service時,該方法被回撥。一旦Service啟動,它會在後臺獨立執行。當Service執行完以後,需呼叫stopSelf()stopService()方法停止Service。(若您只希望bind Service,則無需呼叫這些方法)
  • onBind():當其他元件呼叫bindService()方法請求繫結Service時,該方法被回撥。該方法返回一個IBinder介面,該介面是Service與繫結的元件進行互動的橋樑。若Service未繫結其他元件,該方法應返回null
  • onCreate():當Service第一次建立時,回撥該方法。該方法只被回撥一次,並在onStartCommand()onBind()方法被回撥之前執行。若Service處於執行狀態,該方法不會回撥。
  • onDestroy():當Service被銷燬時回撥,在該方法中應清除一些佔用的資源,如停止執行緒、接觸繫結註冊的監聽器或broadcast receiver 等。該方法是Service中的最後一個回撥。

如果某個元件通過呼叫startService()啟動了Service(系統會回撥onStartCommand()方法),那麼直到在Service中手動呼叫stopSelf()方法、或在其他元件中手動呼叫stopService()方法,該Service才會停止。

如果某個元件通過呼叫bindService()繫結了Service(系統不會回撥onStartCommand()方法),只要該元件與Service處於繫結狀態,Service就會一直執行,當Service不再與元件繫結時,該Service將被destroy

當系統記憶體低時,系統將強制停止Service的執行;若Service繫結了正在與使用者互動的activity,那麼該Service將不大可能被系統killless likely to be killed)。如果建立的是前臺Service,那麼該Service幾乎不會被killalmost never be killed)。否則,當建立了一個長時間在後臺執行的Service後,系統會降低該Service在後臺任務棧中的級別——這意味著它容易被killlower its position in the list of background tasks over time and the service will become highly susceptible to killing),所以在開發Service時,需要使Service變得容易被restart,因為一旦Servicekill,再restart它需要其資源可用時才行(restarts it as soon as resources become available again ),當然這也取決於onStartCommand()方法返回的值,這將在後續介紹。


manifest檔案中註冊serviceDeclaring a service in the manifest

manifest檔案中註冊service的方式如下:
在這裡插入圖片描述

除此之外,在<service>標籤中還可以配置其他屬性,比如,需要啟動該service所需的許可權、該service應執行在哪個程式中 等( permissions required to start the service and the process in which the service should run)。android:name屬性是唯一不可預設的,它指定了Service的全限定類名。一旦釋出了應用,該類名將不可更改。

!請注意:為了保證應用的安全,請使用顯式Intent啟動或繫結一個Service,請不要在<service>標籤中配置intent-filter

若不確定該啟動哪個Service,那麼可以在<service>中配置intent-filter,並在Intent中排除該Servicesupply intent filters for your services and exclude the component name from the Intent),但必須呼叫IntentsetPackage()方法,來為啟動的service消除歧義(provides sufficient disambiguation for the target service)。

注:setPackage()方法傳入一個String引數,代表一個包名。該方法表示該Intent物件只能在傳入的這個包名下尋找符合條件的元件,若傳入null,則表示可以在任意包下尋找。

android:exported屬性設為false,表示不允許其他應用程式啟動本應用的元件,即便是顯式Intent也不行(even when using an explicit intent)。這可以防止其他應用程式啟動您的service元件。

使用start方式啟動ServiceCreating a Started Service

其他元件呼叫startService()方法可以啟動一個Service,接著,Service會回撥onStartCommand()生命週期方法。startService()方法中傳入一個Intent引數,用於顯式指定目標Service的名字,並攜帶data以供Service使用,該Intent引數將回傳至onStartCommand()方法中。
比如說,Activity需要向線上資料庫中上傳資料,那麼可以呼叫startService()啟動一個Service,並將資料傳入Intentdata中,接著,onStartCommand()方法會接收這個Intent並開啟一個執行緒將資料上傳至網路,當資料上傳完成後,該Service將停止並被destroy

一般使用如下兩種方式建立一個start Service

  • 繼承Service類:請務必在Service中開啟執行緒來執行耗時操作,因為Service執行在主執行緒中。

  • 繼承IntentService類:IntentService繼承於Service,若Service不需要同時處理多個請求,那麼使用IntentService將是最好選擇:您只需要重寫onHandleIntent()方法,該方法接收一個回傳的Intent引數,您可以在方法內進行耗時操作,因為它預設開啟了一個子執行緒,操作執行完成後也無需手動呼叫stopSelf()方法,onHandleIntent()會自動呼叫該方法。

繼承IntentService類(Extending the IntentService class

在大多數情況下,start Service並不會同時處理多個請求(don’t need to handle multiple requests simultaneously),因為處理多執行緒較為危險(a dangerous multi-threading scenario),所以繼承IntentService類帶建立Service是個不錯選擇。

使用IntentService的要點如下:

  • 預設在子執行緒中處理回傳到onStartCommand()方法中的Intent
  • 在重寫的onHandleIntent()方法中處理按時間排序的Intent佇列,所以不用擔心多執行緒(multi-threading)帶來的問題。
  • 當所有請求處理完成後,自動停止service,無需手動呼叫stopSelf()方法;
  • 預設實現了onBind()方法,並返回null
  • 預設實現了onStartCommand()方法,並將回傳的Intent以序列的形式傳送給onHandleIntent(),您只需重寫該方法並處理Intent即可。

綜上所述,您只需重寫onHandleIntent()方法即可,當然,還需要建立一個構造方法,示例如下:
在這裡插入圖片描述
如果您還希望在IntentService的 繼承類中重寫其他生命週期方法,如onCreate()onStartCommand()onDestroy(),那麼請先呼叫各自的父類方法以保證子執行緒能夠正常啟動。

比如,要實現onStartCommand()方法,需返回其父類方法:
在這裡插入圖片描述
onHandleIntent()外,onBind()方法也無需呼叫其父類方法。

繼承Service類(Extending the Service class

如果您需要在Service中執行多執行緒而不是處理一個請求佇列(perform multi-threading instead of processing start requests through a work queue),那麼需要繼承Service類,分別處理每個Intent

Service中執行操作時,處理每個請求都需要開啟一個執行緒,並且同一時刻一個執行緒只能處理一個請求( for each start request, it uses a worker thread to perform the job and processes only one request at a time)。
在這裡插入圖片描述
在這裡插入圖片描述

注意到onStartCommand()返回一個整形變數,該變數必須是下列常量之一:

  • START_NOT_STICKY:若執行完onStartCommand()方法後,系統就kill了service,不要再重新建立service,除非系統回傳了一個pending intent。這避免了在不必要的時候執行service,您的應用也可以restart任何未完成的操作。
  • START_STICKY:若系統在onStartCommand()執行並返回後killservice,那麼service會被recreate並回撥onStartCommand()dangerous不要重新傳遞最後一個Intentdo not redeliver the last intent)。相反,系統回撥onStartCommand()時回傳一個空的Intent,除非有 pending intents傳遞,否則Intent將為null。該模式適合做一些類似播放音樂的操作。
  • START_REDELIVER_INTENT:若系統在onStartCommand()執行並返回後killservice,那麼service會被recreate並回撥onStartCommand()並將最後一個Intent回傳至該方法。任何 pending intents都會被輪流傳遞。該模式適合做一些類似下載檔案的操作。

啟動服務(Starting a Service)

若需要啟動Service,見下面所示:
在這裡插入圖片描述

startService(intent)方法將立即返回,並回撥onStartCommand()(請不要手動呼叫該方法),若該Service未處於執行狀態,系統將首先回撥onCreate(),接著再回撥onStartCommand()。若您希望Service可以返回結果,那麼需要通過呼叫getBroadcast 返回的PendingIntent啟動Service(將PendingIntent包裝為Intent),service可使用broadcast 傳遞結果。

多個啟動Service的請求可能導致onStartCommand()多次呼叫,但只需呼叫stopSelf()stopService()這兩個方法之一,就可停止該服務。

停止服務(Stopping a service)

一個啟動的Service必須管理自己的生命週期。系統不會主動stopdestroy一個執行的Service,除非系統記憶體緊張,否則,執行完onStartCommand()方法後,Service依然執行。停止Service必須手動呼叫stopSelf()(在Service中)或呼叫stopService()(在啟動元件中)。

一旦呼叫了上述兩種方法之一,系統會盡快destroyServiceas soon as possible)。

若系統正在處理多個呼叫onStartCommand()請求,那麼在啟動一個請求時,您不應當在此時停止該Serviceyou shouldn’t stop the service when you’re done processing a start request)。為了避免這個問題,您可以呼叫stopSelf(int)方法,以確保請求停止的Service時最新的啟動請求( your request to stop the service is always based on the most recent start request)。這就是說,當呼叫stopSelf(int)方法時,傳入的ID代表啟動請求(該ID會傳遞至onStartCommand()),該ID與請求停止的ID一致。則如果在呼叫stopSelf(int)之前,Service收到一個新的Start請求,ID將無法匹配,Service並不會停止。

為了節省記憶體和電量,當Service完成其工作後將其stop很有必要。如有必要,可以在其他元件中呼叫stopService()方法,即便Service處於繫結狀態,只要它回撥過onStartCommand(),也應當主動停止該Service

建立繫結Service(Creating a Bound Service)

通過其他元件呼叫bindService()方法可以繫結一個Service以保持長連線(long-standing connection),這時一般不允許其他元件呼叫startService()啟動Service

當其他元件需要與Service互動或者需要跨程式通訊時,可以建立一個bound Service

為建立一個bound Service,必須重寫onBind()回撥,該方法返回一個IBinder介面。該介面時元件與Service通訊的橋樑。元件呼叫bindService()Service繫結,該元件可獲取IBinder介面,一旦獲取該介面,就可以呼叫Service中的方法。一旦沒有元件與Service繫結,系統將destroy它,您不必手動停止它。

為建立一個bound Service,必須定義一個介面 ,該介面指定元件與Service如何通訊。定義的介面在元件與Service之間,且必須實現IBinder介面。這正是onBind()的返回值。一旦元件接收了IBinder,元件與Service便可以開始通訊。

多個元件可同時與Service繫結,當元件與Service互動結束後,可呼叫unbindService()方法解綁。bound Servicestart Service要複雜,故我將在後續單獨翻譯。

向使用者傳送通知(Sending Notifications to the User)

執行中的Service可以通過Toast NotificationsStatus Bar Notifications 向使用者傳送通知。Toast是一個可以短時間彈出的提醒框。二Status Bar是頂部狀態列中出現的太有圖示的資訊,使用者可以通過下拉狀態列獲得具體資訊並執行某些操作(如啟動Activity)。

通常,Status Bar用於通知某些操作已經完成,如下載檔案完成。當使用者下拉狀態列後,點選該通知,可獲取詳細內容,如檢視該下載的檔案。

執行前臺ServiceRunning a Service in the Foreground

前臺Service用於動態通知訊息,如天氣預報。該Service不易被kill。前臺Service必須提供status bar,只有前臺Servicedestroy後,status bar才能消失。

舉例來說,一個播放音樂的Service必須是前臺Service,只有這樣使用者才能確知其執行狀態。為前臺Service提供的status bar可以顯示當前音樂的播放狀態,並可以啟動播放音樂的Activity

呼叫startForeground()可以啟動前臺Service。該方法接收兩個引數,引數一是一個int型變數,使用者指定該通知的唯一性標識,而引數而是一個Notification用於配置status bar,示例如下:
在這裡插入圖片描述

!注意:為startForeground()設定的ID必須是0。

呼叫stopForeground()來移除(remove)前臺Service。該方法需傳入一個boolean型變數,表示是否也一併清除status bar上的notificationindicating whether to remove the status bar notification as well)。該方法並不停止Service,如果停止正在前臺執行的Service,那麼notification 也一併被清除。

Service生命週期(Managing the Lifecycle of a Service

Service的啟動到銷燬,有兩種路徑:

  • A started service:需手動停止
  • A bound service:可自動停止

如下圖所示 :
在這裡插入圖片描述

這兩條路徑並不是毫不相干的:當呼叫startService() start一個Service後,您仍可以bindService。比如,當播放音樂時,需呼叫startService()啟動指定播放的音樂,當需要獲取該音樂的播放進度時,有需要呼叫bindService(),在這種情況下,知道Serviceunbind ,呼叫stopService()stopSelf()都不能停止該Service

實現Service的生命週期回撥(Implementing the lifecycle callbacks

在這裡插入圖片描述
這些生命週期方法在使用時無需呼叫各自的父類方法。

在兩條生命週期路徑中,都包含了兩個巢狀的生命週期:

  • 完整生命週期( entire lifetime):從onCreate()被呼叫到onDestroy()返回。與Activity類似,一般在onCreate()中做一些初始化工作,而在onDestroy()做一些資源釋放工作。如,若Service在後臺播放一個音樂,就需要在onCreate()方法中開啟一個執行緒啟動音樂,並在onDestroy()中結束執行緒。-

無論是startService() 還是 bindService()啟動ServiceonCreate()onDestroy()均會被回撥。

  • 活動生命週期(active lifetime):從onStartCommand()onBind()回撥開始。由相應的startService()bindService()呼叫。
    若是Start Service,那麼Service的活動生命週期結束就意味著其完整生命週期結束 (the active lifetime ends the same time that the entire lifetime ends),即便onStartCommand()返回後,Service仍處於活動狀態;若是bound Service,那麼當onUnbind()返回時,Service的活動生命週期結束。
    !請注意:針對Start Service,由於Service中沒有類似onStop()的回撥,所以在呼叫stopSelf()stopService()後,只有onDestroy()被回撥標誌著Service已停止。

相關文章