概述
Service 是 Android 的四大組件之一,它主要的作用是后臺執行操作,Activity 屬于帶有 UI 界面跟用戶進行交互,而 Service 則沒有 UI 界面,所有的操作都是基于后臺運行完成。并且 Service 跟 Activity 一樣也是可以由其它的應用程序調用啟動的,而且就算用戶切換了應用程序,Service 依舊保持運行。一個組件如果與 Service 進行了綁定( bind ),就可以跟 Service 進行數據的交互,并且也可以跟不同的進程之間進行交互 (IPC)。通常會使用到 Service 的情況有進行網絡請求,音樂的操控,文件的 I/O 操作等。
啟動
Service 通常是通過以下兩種方式進行啟動
startService
當組件(例如 activity)通過調用 startService()
來啟動 Service 的時候。一旦啟動后,Service 就會獨立的在后臺運行,即使調用的組件已經銷毀了,Service 還是可以繼續在后臺運行。一般情況下,只需要進行一次單獨的操作,不需要將操作后的結果返回給調用者的時候,會使用該方式啟動 Service。例如,進行上傳或者下載操作的時候,當操作完成后,Service 應該自行調用 stopService()
或 stopSelf()
來結束運行。
bindService
當組件(例如 activity)通過調用 bindService()
來啟動 Service 的時候。這種方式提供了 client - service 的接口,可以讓調用組件跟 Service 進行發送請求及返回結果的操作,設置可以進行進程間的通信 (IPC)。只要有一個組件對該 Service 進行了綁定,那該 Service 就不會銷毀。并且多個組件可以同時對一個 Service 進行綁定,只有在所有進行了綁定的組件都解綁的時候,Service 才會銷毀。
盡管兩種方式是分開討論的,但是并不是互斥的關系,使用 startService
啟動了 Service 后,也是可以進行綁定的。
注意: 雖然 Service 是在后臺運行,但是其實還是在主線程里進行所有的操作的。Service 在啟動時除非單獨進行了定義否則并沒有在單獨的線程或者進程了而都是在主線程里。所以這表示任何能堵塞主線程的操作(例如音樂的播放或者網絡請求)都應該單獨開辟新的線程來進行操作,否則很容易出現 ANR 。
方法
在創建一個 Service 時,必須要去繼承 Service,并且要重寫父類一些主要的方法來實現功能。以下是主要方法的介紹
onStartCommand()
系統會調用這個函數當某個組件(例如 activity,fragment)通過調用 startService()
啟動 Service 時。在該方法被調用后,Service 就會被啟動并獨立的在后臺運行。如果重寫了該方法,開發者需要在 Service 執行完操作后自行的調用 stopSelf()
或 stopService()
,來結束 Service。如果只是會通過綁定的方式 (bind) 的方式來啟動 Service 則不需要重寫該方法。
onBind()
系統會調用這個函數當某個組件(例如 activity, fragment)通過調用 bindService()
綁定的方式來啟動 Service 的時候。在實現這個函數的時候,必須要返回一個 IBinder 的繼承類,來與 Service 進行通信。這個函數是默認必須要重寫的,但是如果不想通過綁定的方式來啟動 Service,則可以直接返回 null
onCreate()
系統會調用此方法在第一次啟動 Service 的時候,用于初始化一些一次性的變量。如果 Service 已經啟動了,則此方法就不會再別調用。
onDestroy()
系統在 Service 已經不需要準備被銷毀的時候會調用此方法。Service 中如有用到 thread
、listeners
、receivers
等的時候,應該將這些的清理方法寫在此方法內。
以上就是實現一個 Service 中要實現的一些方法。
如果某個組件是通過調用 startService()
的方式來啟動了 Service,那這個 Service 就會一直在后臺運行直到 Service 內部調用 stopSelf()
或某個組件調用 stopService()
來結束該 Service。
如果某個組件是通過調用 bindService()
的方式來啟動了 Service,那這個 Service 就會一直在后臺運行直到該組件與其解綁。Service 在沒有任何組件綁定的時候,系統會將其銷毀
下面的環節,將介紹如果通過上面講述的兩種方式來創建 Service
在 Manifest 里聲明 Service
類似于 Activity
,所有的 Service 都要在 Manifest 里面進行聲明,如下:
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
查看 <service> 標簽的官方文檔來獲取更多信息
通過在 <service>
標簽里將 android:exported
設置為 false
。可以防止其他的程序來啟動你的 Service。
通過 started 方式來啟動 Service
組件(例如 activity, fragment) 通過調用 startService()
方法,系統隨之調用 onStartCommand()
方法來實現 started
方式啟動 Service。
當 Service 以該形式啟動后,Service 的整個生命周期是完全獨立的,即便啟動 Service 的組件已經被銷毀了,Service 還是可以在后臺無限的運行的。但開發者應該在 Service 中的操作執行完成后,調用 stopSelf()
或其它組件調用 stopService()
的方式來結束該 Service。
程序組件(例如 activity) 可以通過傳遞一個 Intent 給 startService()
,來實現組件與 Service 之前的數據傳遞。Service 是通過系統調用的 onStartCommand()
方法接受傳遞的Intent
,完成整個數據傳遞過程。
注意: Service 本身默認是運行在主線程里的,所以如果在 Service 要進行一些會堵塞線程的操作,一定要將這些操作放在一個新的線程里。
Android 的框架提供了 IntentService 來滿足后臺運行異步線程的需求。
IntentService
IntentService 是 Service 的子類,并且所有的請求操作都是在異步線程里。如果不需要 Service 來同時處理多個請求的話,IntentService 將會是最佳的選擇。只需要繼承并重寫 IntentService 中的 onHandleIntent()
方法,就可以對接受到的 Intent
做后臺的異步線程操作了。
IntentService 提供了如下的幾個功能:
- 會創建一個異步線程來處理所有從程序主線程發送到
onStartCommand()
的 intents 。 - 創建了一個隊列池來保證每次只有一個 Intent 傳遞到
onHandleIntent()
方法中,避免了多線程問題。 - 提供默認
onBind()
方法的實現,返回 null - 提供默認
onStartCommand()
方法的實現,將收到的 intent 放到隊列池中,然后再交由onHandleIntent()
做處理
所有開發者只需要實現 onHandleIntent()
關注于 Service 要進行的操作即可。關于更多的 IntentService 實用技巧,請查看此文章。
如果開發者需要重寫其他的一些方法,例如 onCreate(), onStartCommand(), 和 onDestroy(),請保證調用父類的實現,這樣可以保證 IntentService 能夠正確的來處理線程的生命周期。例如:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
系統回收資源問題
當系統內存不足的時候,系統會強制回收一些 Activity 和 Service 來獲取更多的資源給那些用戶正在交互的程序或頁面。這就要求 Service 能夠自動重啟當資源充足的時候。這個功能是通過 onStartCommand()
的返回值來實現的
START_NOT_STICKY
當系統因回收資源而銷毀了 Service,當資源再次充足時不自動啟動 Service。除非還有為處理的 Intent 準備發送。當你的程序可以很容易的重新開啟未完成的操作時,這是最安全的避免 Service 在不必要的情況下啟動的選項。
START_STICKY
當系統因回收資源而銷毀了 Service,當資源再次充足時自動啟動 Service,并且再次調用 onStartCommand()
方法,但是不會傳遞最后一次的 Intent,相反系統在回調onStartCommand()
的時候會傳一個空 Intent 。除非還有為處理的 Intent 準備發送。
START_REDELIVER_INTENT
當系統因回收資源而銷毀了 Service,當資源再次充足時自動啟動 Service,并且再次調用 onStartCommand()
方法,并會把最后一次 Intent 再次傳遞給,onStartCommand()
,相應的在隊列里的 Intent 也會按次序一次傳遞。此模式適用于下載等服務。
Start Service
該方式允許多個組件同時對相同的 Service 進行 startService
操作,但是如果只要有其中有一個組件調用了 stopSelf()
或 stopService()
, 該 Service 就會被銷毀。
Intent intent = new Intent(this, HelloService.class);
startService(intent);
Stop Service
當有多個組件進行了 startService
操作時,不應該直接的去調用 stopSelf()
或 stopService()
來結束 Service, 因為這會對其他已經發起請求的操作產生影響,故在 onStartCommand()
方法中會接受一個 startId
, 然后在結束 Service 時,調用 stopService(int)
方法來只是結束一個特定的請求,從而達到保護其他請求不受影響的目的。
通過 Bind 方式啟動 Service
當應用程序中的 activity 或其它組件需要與服務進行交互,或者應用程序的某些功能需要暴露給其它應用程序時,你應該創建一個 Bind 服務,并通過進程間通信(IPC)來完成。
Service 只在為綁定的應用程序組件工作時才會存活,因此,只要沒有組件綁定到服務,系統就會自動銷毀服務
獲取 IBinder 實例
擴展 Binder 類
如果服務是你的應用程序所私有的,并且與客戶端運行于同一個進程中(通常都是如此),你應該通過擴展 Binder 類來創建你的接口,并從 onBind()
返回一個它的實例。客戶端接收該 Binder
對象并用它來直接訪問 Binder
甚至 Service
中可用的公共方法。
如果你的服務只是為你自己的應用程序執行一些后臺工作,那這就是首選的技術方案。不用這種方式來創建接口的理由只有一個,就是服務要被其它應用程序使用或者要跨多個進程使用。
使用 Messenger
如果你需要接口跨越多個進程進行工作,可以通過 Messenger
來為服務創建接口。在這種方式下,服務定義一個響應各類消息對象 Message
的 Handler
。此 Handler
是 Messenger
與客戶端共享同一個 IBinder
的基礎,它使得客戶端可以用消息對象 Message
向服務發送指令。此外,客戶端還可以定義自己的 Message
,以便服務能夠往回發送消息。
這是執行進程間通信(IPC)最為簡便的方式,因為 Messenger
會把所有的請求放入一個獨立進程中的隊列,這樣你就不一定非要把服務設計為線程安全的模式了。
使用 AIDL
絕大多數應用程序都不應該用 AIDL 來創建 bind 服務,因為這可能需要多線程處理能力并且會讓代碼變得更為復雜。 因此,AIDL 對絕大多數應用程序都不適用,并且本文也不會討論如何在服務中使用它的內容。如果你確信需要直接使用 AIDL,那請參閱 AIDL 文檔。
擴展 Binder 類
如果你的服務只用于本地應用程序并且不需要跨進程工作,那你只要實現自己的 Binder 類即可,這樣你的客戶端就能直接訪問服務中的公共方法了。
注意:僅當客戶端和服務位于同一個應用程序和進程中,這也是最常見的情況,這種方式才會有用。比如,一個音樂應用需要把一個 activity 綁定到它自己的后臺音樂播放服務上,采用這種方式就會很不錯。
以下是設置步驟:
- 在你的服務中,創建一個 Binder 的實例,其中實現以下三者之一:
- 包含了可供客戶端調用的公共方法
- 返回當前 Service 實例,其中包含了可供客戶端調用的公共方法
- 或者,返回內含 Service 類的其它類的一個實例,Service 中包含了可供客戶端調用的公共方法
- 從回調方法 onBind() 中返回 Binder 的該實例
- 在客戶端中,在回調方法 onServiceConnected() 中接收 Binder 并用所提供的方法對綁定的服務進行調用
注意:
服務和客戶端之所以必須位于同一個應用程序中,是為了讓客戶端能夠正確轉換(cast)返回的對象并調用對象的 API。 服務和客戶端也必須位于同一個進程中,因為這種方式不能執行任何跨進程的序列化(marshalling)操作。
具體的實用案例請查看在ApiDemos 中的 LocalService.java 類和 LocalServiceActivities.java。
使用 Messenger
以下概括了Messenger的使用方法:
- 服務實現一個
Handler
,用于客戶端每次調用時接收回調 - 此
Handler
用于創建一個Messenger
對象(它是一個對Handler
的引用) - 此
Messenger
對象創建一個IBinder
,服務在 onBind() 中把它返回給客戶端 - 客戶端用
IBinder
將Messenger
(引用服務的Handler
)實例化,客戶端用它向服務發送消息對象Message
- 服務接收
Handler
中的每個消息Message
——確切的說,是在 handleMessage() 方法中接收
在 MessengerService.java(服務)和 MessengerServiceActivities.java(客戶端)例程中,可以看到如何關于 Messenger 的實用例子。