服務總覽

Service 是一種應用程式元件,可在背景執行長時間執行的作業。而不會提供使用者介面。啟動後,服務可能會持續執行一段時間,即便使用者切換至其他應用程式也一樣。此外,元件也可以繫結至服務以與其互動,甚至執行處理序間通訊 (IPC)。舉例來說,服務可以在背景處理網路交易、播放音樂、執行檔案 I/O,或與內容供應器互動。

注意:服務會在其託管程序的主要執行緒中執行;除非另有指定,否則服務「不會」建立自己的執行緒,也不會在獨立程序中執行。您應對服務中的個別執行緒執行任何封鎖作業,以免發生應用程式無回應 (ANR) 錯誤。

服務類型

以下是三種不同的服務類型:

前景

前景服務會執行使用者可察覺的作業。舉例來說,音訊應用程式會使用前景服務播放音軌。前景服務必須顯示「通知」。即使使用者未與應用程式互動,前景服務仍會繼續執行。

使用前景服務時,您必須顯示通知,讓使用者知道服務正在執行。除非服務已停止或從前景移除,否則無法關閉這項通知。

進一步瞭解如何在應用程式中設定前景服務

注意: WorkManager API 可讓您靈活地安排工作,也可以視需要將這些工作做為前景服務執行。在許多情況下,最好使用 WorkManager 直接使用前景服務。

背景
背景服務執行使用者未直接察覺的作業。舉例來說,如果應用程式使用某項服務來縮減儲存空間,這通常會是背景服務。

注意:如果應用程式指定的 API 級別為 26 以上,當應用程式本身並非在前景運作時,系統會對執行背景服務設下限制。例如,在大多數情況下,您不應從背景存取位置資訊。請改為使用 WorkManager 安排工作

已繫結
當應用程式元件透過呼叫 bindService() 繫結至服務元件時,服務會「繫結」。繫結服務提供用戶端伺服器介面,可讓元件透過處理序間通訊 (IPC) 與服務互動、傳送要求、接收結果,甚至執行不同程序。繫結服務只會在另一個應用程式元件繫結的情況下執行。多個元件可以一次繫結至服務,但當所有元件解除繫結時,服務就會遭到刪除。

雖然本說明文件通常會單獨討論啟動和繫結的服務,但您的服務能以這兩種方式運作,也就是啟動 (無限期執行) 以及允許繫結。您只要實作以下兩種回呼方法即可:onStartCommand() 允許元件啟動,而 onBind() 允許繫結。

無論您的服務已啟動、繫結,或兩者皆是,任何應用程式元件皆可使用服務 (即使是由獨立應用程式提供),方式與任何元件都能使用活動的方式相同,只要使用 Intent 啟動服務即可。不過,您可以在資訊清單檔案中將服務宣告為「私人」,並禁止其他應用程式存取。詳情請參閱在資訊清單中宣告服務一節。

選擇服務和執行緒

服務只是可以在背景執行的元件,即便使用者未與應用程式互動也一樣,因此只有在需要時,您才應該建立服務。

如果您必須在主執行緒外執行作業,但只有在使用者與應用程式互動時,您應該改為在其他應用程式元件的環境中建立新的執行緒。舉例來說,如果想要播放音樂,但只在活動執行時,您可以在 onCreate() 建立執行緒,在 onStart() 中開始執行,並在 onStop() 中停止。我們也建議您使用 java.util.concurrent 套件或 Kotlin 協同程式中的執行緒集區和執行工具,而非傳統的 Thread 類別。如要進一步瞭解如何將執行作業移至背景執行緒,請參閱 Android 上的執行緒文件。

請記住,如果您使用服務,預設仍會在應用程式的主執行緒中執行,因此如果服務會執行密集或封鎖作業,您仍應在服務中建立新的執行緒。

基本概念

如要建立服務,您必須建立 Service 的子類別,或使用其現有的子類別。在實作中,您必須覆寫某些回呼方法,以便處理服務生命週期的重要面向,並提供可讓元件繫結至服務的機制 (如適用)。這些是應覆寫的最重要回呼方法:

onStartCommand()
當其他元件 (例如活動) 要求啟動服務時,系統會呼叫 startService() 來叫用這個方法。執行這個方法時,服務會啟動,並且可以無限期在背景執行。如果實作這個方法,您有責任在工作完成後呼叫 stopSelf()stopService() 來停止服務。如果您只想提供繫結,就不需要實作這個方法。
onBind()
當另一個元件想要與服務繫結 (例如執行遠端程序呼叫 (RPC)) 時,系統會呼叫 bindService() 來叫用這個方法。實作這個方法時,必須提供一個介面,讓用戶端透過傳回 IBinder 來與服務通訊。您必須一律實作此方法;然而,如果不想允許繫結,則應傳回空值。
onCreate()
系統會在服務初始建立時 (在呼叫 onStartCommand()onBind() 之前),叫用此方法來執行一次性設定程序。如果服務已在執行,則不會呼叫此方法。
onDestroy()
當服務不再使用且即將遭到刪除時,系統會叫用此方法。您的服務應實作這個方式來清除任何資源,例如執行緒、已註冊的事件監聽器或接收器。這是服務最後收到的呼叫。

如果元件透過呼叫 startService() 啟動服務 (導致呼叫 onStartCommand()),服務會繼續執行,直到其透過 stopSelf() 或其他元件呼叫 stopService() 停止服務為止。

如果元件呼叫 bindService() 來建立服務,且「沒有」呼叫 onStartCommand(),則只有在元件繫結至該元件時,服務才會執行。當服務與所有用戶端解除繫結後,系統會刪除服務。

Android 只有在記憶體不足時才會停止服務,而且必須復原具有使用者焦點的活動的系統資源。如果服務繫結至使用者聚焦的活動,就不太可能遭終止;如果宣告服務在前景執行,就不會被終止。如果服務已經啟動且長時間執行,系統會隨著時間降低其在背景工作清單中的位置,而且服務非常容易終止,如果您的服務已啟動,則必須將其設計為妥善處理系統的重新啟動作業。如果系統終止服務,則服務會在資源可供使用時立即重新啟動,但這也取決於您從 onStartCommand() 傳回的值。如要進一步瞭解系統刪除服務的時機,請參閱處理程序和執行緒文件。

在以下各節中,您將瞭解如何建立 startService()bindService() 服務方法,以及如何透過其他應用程式元件使用這些方法。

在資訊清單中宣告服務

就像處理活動和其他元件一樣,您必須在應用程式的資訊清單檔案中宣告所有服務。

如要宣告服務,請將 <service> 元素新增為 <application> 元素的子項。範例如下:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

如要進一步瞭解如何在資訊清單中宣告服務,請參閱 <service> 元素參考資料。

您可以在 <service> 元素中加入其他屬性來定義屬性,例如啟動服務所需的權限,以及服務執行程序。android:name 屬性是唯一的必要屬性,它會指定服務的類別名稱。發布應用程式後,請勿變更這個名稱,以免因仰賴明確意圖啟動或繫結服務而發生程式碼中斷的風險 (請參閱網誌文章《事物無法變更》。

注意:為確保應用程式安全無虞,啟動 Service 時一律使用明確的意圖,且不要宣告服務的意圖篩選器。使用隱含意圖啟動服務會造成安全性危害,因為您無法確定回應意圖的服務,使用者也無法查看啟動的服務。從 Android 5.0 (API 級別 21) 開始,如果您使用隱含意圖呼叫 bindService(),系統會擲回例外狀況。

您可以加入 android:exported 屬性並將其設為 false,確保服務僅適用於您的應用程式。這樣即使使用明確意圖,也能讓其他應用程式停止啟動您的服務。

注意:使用者可以查看裝置上正在執行的服務,如果使用者不認得或信任某項服務,可以停止該服務。為避免使用者意外停止服務,您必須在應用程式資訊清單中的 <service> 元素中加入 android:description 屬性。在說明中提供簡短句子,說明服務的用途和優點。

建立已啟動的服務

已啟動的服務是指另一個元件透過呼叫 startService() 啟動,進而呼叫服務的 onStartCommand() 方法。

服務啟動後,其生命週期與啟動該服務的元件無關。服務可以無限期在背景執行,即使啟動該服務的元件遭到刪除亦然。因此,服務應在工作完成時透過呼叫 stopSelf() 自行停止,或呼叫 stopService() 的其他元件可以停止服務。

應用程式元件 (例如活動) 可透過呼叫 startService() 並傳遞指定服務的 Intent,並包含服務要使用的任何資料,藉此啟動服務。服務會在 onStartCommand() 方法中接收這個 Intent

舉例來說,假設某個活動需要將部分資料儲存至線上資料庫。該活動可以啟動隨附服務,並傳遞意圖至 startService() 來傳送要儲存的資料。服務在 onStartCommand() 中接收意圖,連線至網際網路並執行資料庫交易。交易完成後,服務會自行停止並刪除。

注意:服務會在與應用程式宣告程序相同的程序中執行,而且根據預設,在該應用程式的主要執行緒中。如果您的服務會在使用者與同一應用程式的活動互動時執行密集或封鎖作業,則服務會減慢活動效能。為避免影響應用程式效能,請在服務中啟動新的執行緒。

Service 類別是所有服務的基本類別。擴充這個類別時,請務必建立新的執行緒,讓服務能夠完成其所有工作。根據預設,服務會使用應用程式的主要執行緒,這可能會減緩應用程式執行中任何活動的效能。

Android 架構也提供 ServiceIntentService 子類別,其會使用工作站執行緒來處理所有啟動要求,一次一個。由於背景執行限制的緣故,自 Android 8 Oreo 起,此類別將不適用於新的應用程式,因此不建議新應用程式使用這個類別。此外,從 Android 11 開始已淘汰。您可以使用 JobIntentService 做為與新版 Android 相容的 IntentService 替代方法。

以下各節將說明如何實作自己的自訂服務,但在大多數用途中,還是建議您考慮改用 WorkManager。請參閱 Android 背景處理指南,瞭解是否有符合您需求的解決方案。

擴充 Service 類別

您可以擴充 Service 類別來處理每個傳入的意圖。基本導入的運作方式如下:

Kotlin

class HelloService : Service() {

    private var serviceLooper: Looper? = null
    private var serviceHandler: ServiceHandler? = null

    // Handler that receives messages from the thread
    private inner class ServiceHandler(looper: Looper) : Handler(looper) {

        override fun handleMessage(msg: Message) {
            // Normally we would do some work here, like download a file.
            // For our sample, we just sleep for 5 seconds.
            try {
                Thread.sleep(5000)
            } catch (e: InterruptedException) {
                // Restore interrupt status.
                Thread.currentThread().interrupt()
            }

            // Stop the service using the startId, so that we don't stop
            // the service in the middle of handling another job
            stopSelf(msg.arg1)
        }
    }

    override fun onCreate() {
        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
            start()

            // Get the HandlerThread's Looper and use it for our Handler
            serviceLooper = looper
            serviceHandler = ServiceHandler(looper)
        }
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()

        // For each start request, send a message to start a job and deliver the
        // start ID so we know which request we're stopping when we finish the job
        serviceHandler?.obtainMessage()?.also { msg ->
            msg.arg1 = startId
            serviceHandler?.sendMessage(msg)
        }

        // If we get killed, after returning from here, restart
        return START_STICKY
    }

    override fun onBind(intent: Intent): IBinder? {
        // We don't provide binding, so return null
        return null
    }

    override fun onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
    }
}

Java

public class HelloService extends Service {
  private Looper serviceLooper;
  private ServiceHandler serviceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // Restore interrupt status.
              Thread.currentThread().interrupt();
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service. Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block. We also make it
    // background priority so CPU-intensive work doesn't disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    serviceLooper = thread.getLooper();
    serviceHandler = new ServiceHandler(serviceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = serviceHandler.obtainMessage();
      msg.arg1 = startId;
      serviceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

程式碼範例會處理 onStartCommand() 中的所有來電,並將工作發布至在背景執行緒上執行的 Handler。運作方式與 IntentService 相同,並依序處理所有要求。您可以變更程式碼,在執行緒集區上執行工作,例如同時執行多個要求。

請注意,onStartCommand() 方法必須傳回整數。整數是一個值,說明系統終止服務時,系統應如何繼續服務。onStartCommand() 的傳回值必須是下列其中一個常數:

START_NOT_STICKY
如果系統在 onStartCommand() 傳回後終止服務,除非有待處理的意圖,請勿重新建立服務。對於非必要的情況,以及應用程式只要重新啟動任何未完成的工作時,這是最安全的選項。
START_STICKY
如果系統在 onStartCommand() 傳回後終止服務,請重新建立服務並呼叫 onStartCommand(),但不要重新提交最後一個意圖。除非有待處理的意圖啟動服務,否則系統會呼叫包含空值意圖的 onStartCommand()。在這種情況下,系統會傳送這些意圖。這適用於不會執行指令,但會無限期執行並等待工作的媒體播放器 (或類似服務)。
START_REDELIVER_INTENT
如果系統在 onStartCommand() 傳回後終止服務,請重新建立服務,並使用最後一個傳送至服務的意圖呼叫 onStartCommand()。系統會依序傳送任何待處理的意圖。這適用於主動執行應立即恢復工作的服務,例如下載檔案。

如要進一步瞭解這些傳回值,請參閱各個常數的連結參考說明文件。

啟動服務

只要將 Intent 傳遞至 startService()startForegroundService(),即可透過活動或其他應用程式元件啟動服務。Android 系統會呼叫服務的 onStartCommand() 方法,並向其傳遞 Intent,後者會指定要啟動的服務。

注意:如果應用程式指定的 API 級別為 26 以上,除非應用程式本身在前景,否則系統會對使用或建立背景服務設下限制。如果應用程式需要建立前景服務,應用程式應呼叫 startForegroundService()。此方法會建立背景服務,但此方法會向系統發出信號,表明服務會將本身升級為前景。服務建立完成後,服務必須在五秒內呼叫其 startForeground() 方法。

舉例來說,活動可以使用 startService() 的明確意圖來啟動上一節 (HelloService) 中的範例服務,如下所示:

Kotlin

startService(Intent(this, HelloService::class.java))

Java

startService(new Intent(this, HelloService.class));

startService() 方法會立即回傳,且 Android 系統會呼叫服務的 onStartCommand() 方法。如果服務尚未執行,系統會先呼叫 onCreate(),然後呼叫 onStartCommand()

如果服務未提供繫結,透過 startService() 提供的意圖,是應用程式元件與服務之間唯一通訊的模式。不過,如果想讓服務傳回結果,啟動服務的用戶端可以為廣播 (使用 getBroadcast()) 建立 PendingIntent,並將其傳送至啟動服務的 Intent 中的服務。服務接著就能使用廣播來傳送結果。

多項啟動服務的要求會導致多次對應的 onStartCommand() 呼叫。不過,透過 stopSelf()stopService() 停止服務只需執行一項要求即可停止服務。

停止服務

啟動的服務必須管理自己的生命週期。也就是說,除非必須復原系統記憶體,且服務在 onStartCommand() 傳回後會繼續執行,否則系統不會停止或刪除服務。服務必須呼叫 stopSelf() 自行停止,或其他元件可以透過呼叫 stopService() 來停止。

一旦要求使用 stopSelf()stopService() 停止,系統會盡快刪除服務。

如果您的服務同時處理對 onStartCommand() 的多項要求,您在處理啟動要求後不應停止服務,因為您可能收到新的啟動要求 (在第一個要求結束時停止,將會終止第二個要求)。如要避免此問題,您可以使用 stopSelf(int),以確保停止服務的要求一律根據最近的啟動要求。也就是說,當您呼叫 stopSelf(int) 時,會將啟動要求 (傳送至 onStartCommand()startId) 傳遞至停止要求所對應的啟動要求 ID。這樣一來,如果服務在您呼叫 stopSelf(int) 前收到新的啟動要求,ID 就會不相符,服務也不會停止。

注意:為了避免浪費系統資源和電池電力,請確保應用程式會在作業完成後停止其服務。如有需要,其他元件可透過呼叫 stopService() 停止服務。即使您為服務啟用繫結,但如果服務收到對 onStartCommand() 的呼叫,則一律必須自行停止服務。

如要進一步瞭解服務的生命週期,請參閱下方的管理服務的生命週期一節。

建立繫結服務

繫結服務可讓應用程式元件藉由呼叫 bindService() 建立長期連線來繫結至該服務。通常不允許元件透過呼叫 startService() 來「啟動」

當您想透過應用程式中的活動和其他元件與服務互動,或透過處理序間通訊 (IPC) 向其他應用程式提供某些應用程式的功能時,請建立繫結服務。

如要建立繫結服務,請實作 onBind() 回呼方法以傳回 IBinder,藉此定義與服務通訊的介面。接著,其他應用程式元件可以呼叫 bindService() 來擷取介面,並開始呼叫服務的方法。服務持續運作,只能提供繫結至該應用程式元件的應用程式元件,因此在沒有任何元件繫結至服務時,系統會刪除該元件。您「不需要」停止繫結服務,方法與透過 onStartCommand() 啟動服務時使用的相同。

如要建立繫結服務,您必須定義介面,指定用戶端與服務通訊的方式。服務和用戶端之間的介面必須是 IBinder 的實作,服務必須透過 onBind() 回呼方法傳回。用戶端收到 IBinder 後,即可開始透過該介面與服務互動。

多個用戶端可以同時繫結至服務。用戶端與服務互動後,會呼叫 unbindService() 來解除繫結。當沒有任何用戶端繫結至服務時,系統會刪除該服務。

實作繫結服務的方法有很多種,而實作方式比啟動的服務更複雜。基於上述原因,繫結服務討論會顯示在有關「Bound Services」的另一份文件中。

傳送通知給使用者

服務執行期間,可以透過 Snackbar 通知狀態列通知通知使用者事件。

Snackbar 通知是訊息顯示在目前視窗介面上,只會在消失前短暫顯示。狀態列通知會在狀態列中提供一個圖示和訊息,使用者可以選取該圖示以執行動作 (例如啟動活動)。

一般來說,如果背景作業 (例如檔案下載完成),且使用者現在可以採取行動,狀態列通知就是最好的運用技巧。當使用者從展開的檢視畫面中選取通知時,通知可能會啟動活動 (例如顯示已下載的檔案)。

管理服務的生命週期

服務的生命週期比活動的生命週期簡單許多。不過,請特別留意服務的建立和刪除方式,因為服務可以在使用者不知情的情況下在背景執行。

從建立到刪除的服務生命週期,可以遵循下列任一路徑:

  • 已啟動的服務

    當另一個元件呼叫 startService() 時,就會建立服務。接著,服務會無限期執行,並必須呼叫 stopSelf() 自行停止。其他元件也可以透過呼叫 stopService() 來停止服務。當服務停止時,系統會刪除該服務。

  • 繫結服務

    當另一個元件 (用戶端) 呼叫 bindService() 時,就會建立服務。接著,用戶端會透過 IBinder 介面與服務通訊。用戶端可呼叫 unbindService() 來關閉連線。您可以將多個用戶端繫結至相同服務,一旦所有用戶端解除繫結,系統就會刪除服務。服務「不」需要自行停止。

這兩種路徑不完全分開。您可以繫結至已開始使用 startService() 的服務。舉例來說,您可以使用識別要播放音樂的 Intent 呼叫 startService(),啟動背景音樂服務。之後,如果使用者想對玩家執行某些控制項或取得目前歌曲的相關資訊,則活動可以透過呼叫 bindService() 繫結至服務。在這種情況下,stopService()stopSelf() 並不會真的停止服務,直到所有用戶端解除繫結為止。

實作生命週期回呼

如同活動,服務具有生命週期回呼方法,您可以實作以監控服務狀態的變更,並在適當時機執行工作。以下架構服務示範了每個生命週期方法:

Kotlin

class ExampleService : Service() {
    private var startMode: Int = 0             // indicates how to behave if the service is killed
    private var binder: IBinder? = null        // interface for clients that bind
    private var allowRebind: Boolean = false   // indicates whether onRebind should be used

    override fun onCreate() {
        // The service is being created
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // The service is starting, due to a call to startService()
        return startMode
    }

    override fun onBind(intent: Intent): IBinder? {
        // A client is binding to the service with bindService()
        return binder
    }

    override fun onUnbind(intent: Intent): Boolean {
        // All clients have unbound with unbindService()
        return allowRebind
    }

    override fun onRebind(intent: Intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }

    override fun onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

Java

public class ExampleService extends Service {
    int startMode;       // indicates how to behave if the service is killed
    IBinder binder;      // interface for clients that bind
    boolean allowRebind; // indicates whether onRebind should be used

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return startMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return binder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return allowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

注意:與活動生命週期回呼方法不同,您「不」需要呼叫這些回呼方法的父類別實作。

圖 2. 服務生命週期。左方圖表顯示使用 startService() 建立服務的生命週期,右圖則是透過 bindService() 建立服務的生命週期。

圖 2 說明服務的一般回呼方法。雖然圖表將 startService() 建立的服務與 bindService() 建立的服務分開,但請注意,無論如何啟動服務,任何服務都可能允許用戶端繫結至該服務。最初由 onStartCommand() 啟動的服務 (用戶端呼叫 startService()) 仍可接收對 onBind() 的呼叫 (當用戶端呼叫 bindService() 時)。

實作這些方法,即可監控服務生命週期的下列兩個巢狀迴圈:

注意:雖然已啟動的服務因呼叫 stopSelf()stopService() 而停止,但服務沒有個別的回呼 (沒有 onStop() 回呼)。除非服務已繫結至用戶端,否則系統會在服務停止時刪除服務。onDestroy() 是唯一收到的回呼。

如要進一步瞭解如何建立提供繫結的服務,請參閱繫結服務文件,如要進一步瞭解 onRebind() 回呼方法,請參閱「管理繫結服務的生命週期」一節。