劉明淵的博客地址:http://blog.csdn.NET/vanpersie_9987
Service是Android中一個類,它是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會在後臺執行單獨的操作,也並不給啓動它的組件返回結果。比如說,一個start的Service執行在後臺下載或上傳一個文件的操作,完成之後,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傳遞參數。當然,您也可以將Service在manifest文件中配置成私有的,不允許其他應用訪問。
!請注意:Service運行在主線程中(A service runs in the main thread of its hosting process),Service並不是一個新的線程,也不是新的進程。也就是說,若您需要在Service中執行較爲耗時的操作(如播放音樂、執行網絡請求等),需要在Service中創建一個新的線程。這可以防止ANR的發生,同時主線程可以執行正常的UI操作。
使用Service還是使用Thread?
Service是一個運行在後臺的組件,並不與用戶交互。您僅在需要的時候創建Service( create a service only if that is what you need)。
當用戶正在與UI交互時,需要執行一些主線程無法完成的工作,應當創建一個線程。例如當activity正在運行時,需要播放音樂,此時需要在Activity的onCreate()
中創建線程並在onStart()
中開啓。最後在onStop()
中停止。您也可以考慮使用AsyncTask
或 HandlerThread
來替代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將不大可能被系統kill( less likely to be killed)。如果創建的是前臺Service,那麼該Service幾乎不會被kill(almost never be killed)。否則,當創建了一個長時間在後臺運行的Service後,系統會降低該Service在後臺任務棧中的級別——這意味着它容易被kill(lower its position in the list
of background tasks over time and the service will become highly susceptible to killing),所以在開發Service時,需要使Service變得容易被restart,因爲一旦Service被kill,再restart它需要其資源可用時才行(restarts it as soon as resources become available again ),當然這也取決於onStartCommand()
方法返回的值,這將在後續介紹。
在manifest文件中註冊service(Declaring 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中排除該Service(supply intent filters for your services and exclude
the component name from the Intent),但必須調用Intent的setPackage()
方法,來爲啓動的service消除歧義(provides
sufficient disambiguation for the target service)。
注:setPackage()
方法傳入一個String參數,代表一個包名。該方法表示該Intent對象只能在傳入的這個包名下尋找符合條件的組件,若傳入null,則表示可以在任意包下尋找。
將android:exported
屬性設爲false,表示不允許其他應用程序啓動本應用的組件,即便是顯式Intent也不行(even when using an explicit intent)。這可以防止其他應用程序啓動您的service組件。
使用start方式啓動Service(Creating a Started Service)
其他組件調用startService()
方法可以啓動一個Service,接着,Service會回調onStartCommand()
生命週期方法。startService()
方法中傳入一個Intent參數,用於顯式指定目標Service的名字,並攜帶data以供Service使用,該Intent參數將回傳至onStartCommand()
方法中。
比如說,Activity需要向在線數據庫中上傳數據,那麼可以調用startService()
啓動一個Service,並將數據傳入Intent的data中,接着,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()
執行並返回後kill了service,那麼service會被recreate並回調onStartCommand()
。dangerous不要重新傳遞最後一個Intent(do not redeliver the last intent)。相反,系統回調onStartCommand()
時回傳一個空的Intent,除非有 pending intents傳遞,否則Intent將爲null。該模式適合做一些類似播放音樂的操作。
START_REDELIVER_INTENT
:若系統在onStartCommand()
執行並返回後kill了service,那麼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必須管理自己的生命週期。系統不會主動stop或destroy一個運行的Service,除非系統內存緊張,否則,執行完onStartCommand()
方法後,Service依然運行。停止Service必須手動調用stopSelf()
(在Service中)或調用stopService()
(在啓動組件中)。
一旦調用了上述兩種方法之一,系統會盡快destroy該Service(as soon as possible)。
若系統正在處理多個調用onStartCommand()
請求,那麼在啓動一個請求時,您不應當在此時停止該Service(you 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 Service比start Service要複雜,故我將在後續單獨翻譯。
向用戶發送通知(Sending Notifications to the User)
運行中的Service可以通過Toast Notifications 或 Status Bar Notifications 向用戶發送通知。Toast是一個可以短時間彈出的提醒框。二Status Bar是頂部狀態欄中出現的太有圖標的信息,用戶可以通過下拉狀態欄獲得具體信息並執行某些操作(如啓動Activity)。
通常,Status Bar用於通知某些操作已經完成,如下載文件完成。當用戶下拉狀態欄後,點擊該通知,可獲取詳細內容,如查看該下載的文件。
運行前臺Service(Running a Service in the Foreground)
前臺Service用於動態通知消息,如天氣預報。該Service不易被kill。前臺Service必須提供status bar,只有前臺Service被destroy後,status bar才能消失。
舉例來說,一個播放音樂的Service必須是前臺Service,只有這樣用戶才能確知其運行狀態。爲前臺Service提供的status bar可以顯示當前音樂的播放狀態,並可以啓動播放音樂的Activity。
調用startForeground()
可以啓動前臺Service。該方法接收兩個參數,參數一是一個int型變量,用戶指定該通知的唯一性標識,而參數而是一個Notification
用於配置status
bar,示例如下:
!注意:爲startForeground()
設置的ID必須是0。
調用stopForeground()
來移除(remove)前臺Service。該方法需傳入一個boolean型變量,表示是否也一併清除status bar上的notification(indicating 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後,您仍可以bind該Service。比如,當播放音樂時,需調用startService()
啓動指定播放的音樂,當需要獲取該音樂的播放進度時,有需要調用bindService()
,在這種情況下,知道Service被unbind
,調用stopService() 或stopSelf()
都不能停止該Service。
實現Service的生命週期回調(Implementing the lifecycle callbacks)
這些生命週期方法在使用時無需調用各自的父類方法。
在兩條生命週期路徑中,都包含了兩個嵌套的生命週期:
- 完整生命週期( entire lifetime ):從
onCreate()
被調用到onDestroy()
返回。與Activity類似,一般在onCreate()
中做一些初始化工作,而在onDestroy()
做一些資源釋放工作。如,若Service在後臺播放一個音樂,就需要在onCreate()
方法中開啓一個線程啓動音樂,並在onDestroy()
中結束線程。-
無論是startService() 還是 bindService()
啓動Service,onCreate()
和 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已停止。