關於 Android Service 的介紹都在這了

騎摩托馬斯發表於2019-03-04

概述

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 中如有用到 threadlistenersreceivers 等的時候,應該將這些的清理方法寫在此方法內

以上就是實現一個 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> 標籤裡將 android:exported 設定為 false。可以防止其他的程式來啟動你的 Service

通過 started 方式來啟動 Service

元件(例如 activity, fragment) 通過呼叫 startService() 方法,系統隨之呼叫 onStartCommand() 方法來實現 started 方式啟動 Service。

當 Service 以該形式啟動後,Service 的整個生命週期是完全獨立的,即便啟動 Service 的元件已經被銷燬了,Service 還是可以在後臺無限的執行的。但開發者應該在 Service 中的操作執行完成後,呼叫 stopSelf() 或其它元件呼叫 stopService() 的方式來結束該 Service。

程式元件(例如 activity) 可以通過傳遞一個 IntentstartService(),來實現元件與 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 來為服務建立介面。在這種方式下,服務定義一個響應各類訊息物件 MessageHandler。此 HandlerMessenger 與客戶端共享同一個 IBinder 的基礎,它使得客戶端可以用訊息物件 Message向服務傳送指令。此外,客戶端還可以定義自己的 Message ,以便服務能夠往回傳送訊息。

這是執行程式間通訊(IPC)最為簡便的方式,因為 Messenger 會把所有的請求放入一個獨立程式中的佇列,這樣你就不一定非要把服務設計為執行緒安全的模式了。

使用 AIDL

絕大多數應用程式都不應該用 AIDL 來建立 bind 服務,因為這可能需要多執行緒處理能力並且會讓程式碼變得更為複雜。 因此,AIDL 對絕大多數應用程式都不適用,並且本文也不會討論如何在服務中使用它的內容。如果你確信需要直接使用 AIDL,那請參閱 AIDL 文件。

擴充套件 Binder 類

如果你的服務只用於本地應用程式並且不需要跨程式工作,那你只要實現自己的 Binder 類即可,這樣你的客戶端就能直接訪問服務中的公共方法了。

注意:僅當客戶端和服務位於同一個應用程式和程式中,這也是最常見的情況,這種方式才會有用。比如,一個音樂應用需要把一個 activity 繫結到它自己的後臺音樂播放服務上,採用這種方式就會很不錯。

以下是設定步驟:

  1. 在你的服務中,建立一個 Binder 的例項,其中實現以下三者之一:
    • 包含了可供客戶端呼叫的公共方法
    • 返回當前 Service 例項,其中包含了可供客戶端呼叫的公共方法
    • 或者,返回內含 Service 類的其它類的一個例項,Service 中包含了可供客戶端呼叫的公共方法
  2. 從回撥方法 onBind() 中返回 Binder 的該例項
  3. 在客戶端中,在回撥方法 onServiceConnected() 中接收 Binder 並用所提供的方法對繫結的服務進行呼叫

    注意:
    服務和客戶端之所以必須位於同一個應用程式中,是為了讓客戶端能夠正確轉換(cast)返回的物件並呼叫物件的 API。 服務和客戶端也必須位於同一個程式中,因為這種方式不能執行任何跨程式的序列化(marshalling)操作。

具體的實用案例請檢視在ApiDemos 中的 LocalService.java 類和 LocalServiceActivities.java

使用 Messenger

以下概括了Messenger的使用方法:

  • 服務實現一個 Handler ,用於客戶端每次呼叫時接收回撥
  • Handler 用於建立一個 Messenger 物件(它是一個對 Handler 的引用)
  • Messenger 物件建立一個 IBinder ,服務在 onBind() 中把它返回給客戶端
  • 客戶端用 IBinderMessenger(引用服務的 Handler)例項化,客戶端用它向服務傳送訊息物件 Message
  • 服務接收 Handler 中的每個訊息 Message ——確切的說,是在 handleMessage() 方法中接收

MessengerService.java(服務)和 MessengerServiceActivities.java(客戶端)例程中,可以看到如何關於 Messenger 的實用例子。

Service 的生命週期

關於 Android Service 的介紹都在這了
service 生命週期圖

相關文章