詳解Android四大組件之——服務


詳解Android四大組件之——服務

關於服務

定義

Service 是一種可在後臺執行長時間運行操作而不提供界面的應用組件。服務可由其他應用組件啟動,而且即使用戶切換到其他應用,服務仍將在後臺繼續運行。此外,組件可通過綁定到服務與之進行交互,甚至是執行進程間通信 (IPC)。例如,服務可在後臺處理網絡事務、播放音樂,執行文件 I/O 或與內容提供程序進行交互。

類型

  • 前臺服務

執行一些用戶能注意到的操作。例如,音頻應用會使用前臺服務來播放音頻曲目。前臺服務必須顯示通知。即使用戶停止與應用的交互,前臺服務仍會繼續運行。

  • 後臺服務

後臺服務執行用戶不會直接注意到的

操作。例如,如果應用使用某個服務來壓縮其存儲空間,則此服務通常是後臺服務。

  • 綁定服務

當應用組件通過調用 bindService() 綁定到服務時,服務即處於綁定狀態。綁定服務會提供客戶端-服務器接口,以便組件與服務進行交互、發送請求、接收結果,甚至是利用進程間通信 (IPC) 跨進程執行這些操作。僅當與另一個應用組件綁定時,綁定服務才會運行。多個組件可同時綁定到該服務,但全部取消綁定後,該服務即會被銷燬。

雖然分開概括討論啟動服務和綁定服務,但服務可同時以這兩種方式運行,它既可以是啟動服務(以無限期運行),亦支持綁定。唯一的問題在於實現一組回調方法:onStartCommand()(讓組件啟動服務)和 onBind()(實現服務綁定)。

如何創建服務

如要創建服務,必須創建 Service 的子類(或使用它的一個現有子類)。在實現中,必須重寫一些回調方法,從而處理服務生命週期的某些關鍵方面,並提供一種機制將組件綁定到服務。

詳解Android四大組件之——服務

主要的回調方法:

  • onStartCommand()

當另一個組件(如 Activity)請求啟動服務時,系統會通過調用 startService() 來調用此方法。執行此方法時,服務即會啟動並可在後臺無限期運行。如果實現此方法,則在服務工作完成後,需通過調用 stopSelf()stopService() 來停止服務。(如果只想提供綁定,則無需實現此方法。)

  • onBind

當另一個組件想要與服務綁定(例如執行 RPC)時,系統會通過調用 bindService() 來調用此方法。在此方法的實現中,必須通過返回 IBinder 提供一個接口,以供客戶端用來與服務進行通信。務必實現此方法;但是,如果並不希望被綁定,則應返回

null

  • onCreate()

首次創建服務時,系統會(在調用 onStartCommand() 或 onBind() 之前)調用此方法來執行一次性設置程序。如果服務已在運行,則不會調用此方法

  • onDestroy()

當不再使用服務且準備將其銷燬時,系統會調用此方法。服務應通過實現此方法來清理任何資源,如線程、註冊的偵聽器、接收器等。這是服務接收的最後一個調用。

除了繼承service實現上訴的幾個重要回調之外,還需要在

清單文件中註冊service:

<manifest>
...
<application>
<service>
...
/<application>
/<manifest>

關於service標籤,可以操作的屬性有:

<service> //向用戶描述服務的字符串 

android:description="string resource"
//服務是否支持直接啟動,即其是否可以在用戶解鎖設備之前運行。
//注:在直接啟動期間,應用中的服務僅可訪問存儲在設備保護存儲區的數據
android:directBootAware=["true" | "false"]
//系統是否可實例化服務 默認為“true”
//<application> 元素擁有自己的 enabled 屬性,該屬性適用於所有應用組件,包括服務
//<application> 和 <service> 屬性都為“true”(因為它們都默認使用該值)時,系統才能啟用服務
android:enabled=["true" | "false"]
//其他應用的組件是否能調用服務或與之交互,默認值取決於服務是否包含 Intent 過濾器,沒有任何過濾器則為false
//當該值為“false”時,只有同一個應用或具有相同用戶 ID 的應用的組件可以啟動服務或綁定到服務
android:exported=["true" | "false"]
//闡明服務是滿足特定用例要求的前臺服務
android:foregroundServiceType=["connectedDevice" | "dataSync" |
"location" | "mediaPlayback" | "mediaProjection" |
"phoneCall"]
//表示服務的圖標,如果未設置該屬性,則轉而使用為應用整體指定的圖標
android:icon="drawable resource"
//如果設置為 true,則此服務將在與系統其餘部分隔離的特殊進程下運行。此服務自身沒有權限,只能通過 Service API 與其進行通信(綁定和啟動)

android:isolatedProcess=["true" | "false"]
//可向用戶顯示的服務名稱,如果未設置該屬性,則轉而使用為應用整體設置的標籤
android:label="string resource"
//實現服務的 Service 子類的名稱
android:name="string"
//實體啟動服務或綁定到服務所必需的權限的名稱如果 startService()、bindService() 或 stopService() 的調用者尚未獲得此權限,該方法將不起作用,且系統不會將 Intent 對象傳送給服務
//如果未設置該屬性,則對服務應用由 <application> 元素的 permission 屬性所設置的權限。如果二者均未設置,則服務不受權限保護
android:permission="string"
//將運行服務的進程的名稱
android:process="string"
>
. . .
/<application>/<service>
/<application>/<application>/<service>

service的活動週期:

  • 通過調用 startService() 啟動服務

這會引起對 onStartCommand() 的調用,則服務會一直運行,直到其使用 stopSelf() 自行停止運行,或由其他組件通過調用 stopService() 將其停止為止

  • 通過調用 bindService() 來創建服務,且未調用 onStartCommand()

服務只會在該組件與其綁定時運行。當該服務與其所有組件取消綁定後,系統便會將其銷燬

關於系統可能停止service的情況:

  • 只有在內存過低且必須回收系統資源以供擁有用戶焦點的 Activity 使用時,Android 系統才會停止服務
  • 如果將服務綁定到擁有用戶焦點的 Activity,則它其不太可能會終止
  • 如果將服務聲明為在前臺運行,則其幾乎永遠不會終止
  • 如果服務已啟動並長時間運行,則系統逐漸降低其在後臺任務列表中的位置,而服務被終止的概率也會大幅提升,如果服務是啟動服務,則必須將其設計為能夠妥善處理系統執行的重啟
  • 如果系統終止服務,則其會在資源可用時立即重啟服務,但這取決於從 onStartCommand() 返回的值
詳解Android四大組件之——服務

onStartCommand()方法的可返回值:

  • START_ NOT_STICKY

如果系統在 onStartCommand() 返回後終止服務,則除非有待傳遞的掛起 Intent,否則系統不會重建服務。這是最安全的選項,可以避免在不必要時以及應用能夠輕鬆重啟所有未完成的作業時運行服務。

  • START_STICKY

如果系統在 onStartCommand() 返回後終止服務,則其會重建服務並調用 onStartCommand(),但不會重新傳遞最後一個 Intent。相反,除非有掛起 Intent 要啟動服務,否則系統會調用包含空 Intent 的 onStartCommand()。在此情況下,系統會傳遞這些 Intent。此常量適用於不執行命令、但無限期運行並等待作業的媒體播放器(或類似服務)。

  • START_ REDELIVER_INTENT

如果系統在 onStartCommand() 返回後終止服務,則其會重建服務,並通過傳遞給服務的最後一個 Intent 調用 onStartCommand()。所有掛起 Intent 均依次傳遞。此常量適用於

主動執行應立即恢復的作業(例如下載文件)的服務。

上面已經提到,創建服務除了繼承Service之外,還可以直接使用它的一個現有子類,那麼這個之類是誰呢?就是下面這位了...

IntentService

關於兩者,他們適應不同的應用場景來實現服務:

  • Service

這是適用於所有服務的基類。擴展此類時,必須創建用於執行所有服務工作的新線程,因為服務默認使用應用的主線程,這會降低應用正在運行的任何 Activity 的性能。

  • IntentService

這是 Service 的子類,其使用工作線程逐一處理所有啟動請求。如果不要求服務同時處理多個請求,此類為最佳選擇。實現 onHandleIntent(),該方法會接收每個啟動請求的 Intent,以便執行後臺工作。

可提供的特性:

  • 創建默認的工作線程,用於在應用的主線程外執行傳遞給 onStartCommand() 的所有 Intent
  • 創建工作隊列,用於將 Intent 逐一傳遞給 onHandleIntent() 實現
  • 在處理完所有啟動請求後停止服務,因此永遠不必調用 stopSelf()
  • 提供 onBind() 的默認實現(返回 null)
  • 提供 onStartCommand() 的默認實現,可將 Intent 依次發送到工作隊列和 onHandleIntent() 實現

擴展實現IntentService需要注意的點:

  • onStartCommand() 必須返回默認實現,從源碼可見緣由:

@Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; }

@Override public void onStart(@Nullable Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); }

  • 實現onHandleIntent()

至此,我們已經知道如何創建服務,那麼創建好的服務又應如何進行開啟、停止和綁定呢?這就是接下來我們要解決的問題...

服務的開啟、停止、綁定

服務的開啟

可以通過將 Intent 傳遞給 startService()startForegroundService(),從 Activity 或其他應用組件啟動服務。Android 系統會調用服務的 onStartCommand() 方法,並向其傳遞 Intent,從而指定要啟動的服務。

至API 26開始,系統對開啟服務有了嚴格的限制:

如果應用面向 API 級別 26 或更高版本,除非應用本身在前臺運行,否則系統不會對使用或創建後臺服務施加限制。如果應用需要創建前臺服務,則其應調用 startForegroundService()。此方法會創建後臺服務,但它會向系統發出信號,表明服務會將自行提升至前臺。創建服務後,該服務必須在五秒內調用自己的 startForeground() 方法。

服務的停止

除非必須回收內存資源,否則系統不會停止或銷燬服務,並且服務在 onStartCommand() 返回後

仍會繼續運行。服務必須通過調用 stopSelf() 自行停止運行,或由另一個組件通過調用 stopService() 來停止它。

一旦請求使用 stopSelf() 或 stopService() 來停止服務,系統便會盡快銷燬服務。

如果服務同時處理多個對 onStartCommand() 的請求,則不應在處理完一個啟動請求之後停止服務,因為可能已收到新的啟動請求(在第一個請求結束時停止服務會終止第二個請求)。為避免此問題,可以使用 stopSelf(int) 確保服務停止請求始終基於最近的啟動請求。換言之,在調用 stopSelf(int) 時,您需傳遞與停止請求 ID 相對應的啟動請求 ID(傳遞給 onStartCommand() 的 startId)。此外,如果服務在能夠調用 stopSelf(int) 之前收到新啟動請求,則 ID 不匹配,服務也不會停止。

服務的綁定

綁定服務允許應用組件通過調用 bindService() 與其綁定,從而創建長期連接。此服務通常不允許組件通過調用 startService() 來啟動它。

如要創建綁定服務,您需通過實現 onBind() 回調方法返回 IBinder,從而定義與服務進行通信的接口。

服務只用於與其綁定的應用組件,因此若沒有組件與該服務綁定,則系統會銷燬該服務。不必像通過 onStartCommand() 啟動的服務那樣,以相同方式停止綁定服務。

詳解Android四大組件之——服務

在前臺運行的服務

前臺服務是用戶主動意識到的一種服務,因此在內存不足時,系統也不會考慮將其終止。前臺服務必須為狀態欄提供通知,將其放在運行中的標題下方。這意味著除非將服務停止或從前臺移除,否則不能清除該通知。

在使用前臺服務時,應注意以下事項:

只有當應用執行的任務需供用戶查看(即使該任務未直接與應用交互)時,才應使用前臺服務。因此,前臺服務必須顯示優先級為 PRIORITY_LOW 或更高的狀態欄通知,這有助於確保用戶知道應用正在執行的任務。如果某操作不是特別重要,因而想使用最低優先級通知,則可能不適合使用服務;相反,可以考慮使用Sceduler。

創建前臺任務,需要請求必要的權限:

如果應用面向 Android 9(API 級別 28)或更高版本並使用前臺服務,則其必須請求 FOREGROUND_SERVICE 權限。這是一種普通權限,因此,系統會自動為請求權限的應用授予此權限。

如果面向 API 級別 28 或更高版本的應用試圖創建前臺服務但未請求 FOREGROUND_SERVICE,則系統會拋出 SecurityException。

創建前臺服務的示例:

val pendingIntent: PendingIntent =
Intent(this, ExampleActivity::class.java).let { notificationIntent ->

PendingIntent.getActivity(this, 0, notificationIntent, 0)
}

val notification: Notification = Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build()
//提供給 startForeground() 的整型 ID 不得為 0
startForeground(ONGOING_NOTIFICATION_ID, notification)

移除前臺服務:

stopForeground (boolean removeNotification) // 是否同時移除通知

此方法不會停止服務。但是,如果服務仍運行於前臺時將其停止,則通知也會隨之移除。

服務的生命週期

與 Activity 類似,服務也擁有生命週期回調方法,可通過實現這些方法來監控服務狀態的變化並適時執行工作。以下展示了每種生命週期方法:

class MyService : Service() {
// 指示服務被終止時的行為
private var startMode: Int = 0
// 綁定客戶端的接口
private var binder: IBinder? = null
// 指示是否應使用onRebind
private var allowRebind: Boolean = false

override fun onCreate() {
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 由於調用startService(),服務正在啟動
return mStartMode
}

override fun onBind(intent: Intent): IBinder? {
// 客戶端使用bindService()綁定到服務
return mBinder
}

override fun onUnbind(intent: Intent): Boolean {
// 所有客戶端都與unbindService()解除綁定
return mAllowRebind
}

override fun onRebind(intent: Intent) {
// 在已經調用onUnbind()之後,客戶端使用bindService()綁定到服務
}

override fun onDestroy() {
}

附上一張經典的service生命週期圖示:

詳解Android四大組件之——服務

關於service的生命週期:

  • 服務的整個生命週期貫穿調用 onCreate() 和返回 onDestroy() 之間的這段時間。與 Activity 類似,服務也在 onCreate() 中完成初始設置,並在 onDestroy() 中釋放所有剩餘資源。例如,音樂播放服務可以在 onCreate() 中創建用於播放音樂的線程,然後在 onDestroy() 中停止該線程。
  • 服務的活動生命週期從調用 onStartCommand() 或 onBind() 開始。每種方法均會獲得 Intent 對象,該對象會傳遞至 startService() 或 bindService()。對於啟動服務,活動生命週期與整個生命週期會同時結束(即便是在 onStartCommand() 返回之後,服務仍然處於活動狀態)。對於綁定服務,活動生命週期會在 onUnbind() 返回時結束。

Andoird8.0開始對後臺Service限制

我們知道,每次在後臺運行時,應用都會消耗一部分有限的設備資源,例如 RAM。 這可能會影響用戶體驗,如果用戶正在使用佔用大量資源的應用(例如玩遊戲或觀看視頻),影響會尤為明顯。 所以,為了提升用戶體驗,從Android 8.0(API 級別 26)開始,對應用在後臺運行時可以執行的操作施加了限制。

需要注意的是:

默認情況下,這些限制僅適用於適配 Android 8.0(API 級別 26)或更高版本的應用。 然而,即使應用適配的 API 級別低於 26,用戶也可以從設置界面,為任意應用啟用其中大多數限制。

不會對綁定 Service 產生任何影響。 如果應用定義了綁定 Service,則不管應用是否處於前臺,其他組件都可以綁定到該 Service。

IntentService 是一項 Service,因此其遵守針對後臺 Service 的新限制。 因此,許多依賴 IntentService 的應用在適配 Android 8.0 或更高版本時無法正常工作。 出於這一原因,Android 支持庫 26.0.0 引入了一個新的JobIntentService類,該類提供與 IntentService 相同的功能,但在 Android 8.0 或更高版本上運行時使用作業而非 Service。

對於我們開發者而言,最直接的影響就是,應用處於空閒狀態時,可以使用的後臺 Service 被限制。 這些限制不適用於前臺 Service,因為前臺 Service 更容易引起用戶注意。

詳解Android四大組件之——服務

那麼什麼是空閒狀態呢?

空閒狀態

處於前臺時,應用可以自由創建和運行前臺與後臺 Service。 進入後臺時,在一個持續數分鐘的時間窗內,應用仍可以創建和使用 Service。 在該時間窗結束後,應用將被視為處於空閒狀態。 此時,系統將停止應用的後臺 Service,就像應用已經調用 Service 的 Service.stopSelf() 方法一樣。

什麼時候應用被視為處於前臺

  • 具有可見 Activity(不管該 Activity 已啟動還是已暫停)。
  • 具有前臺 Service。
  • 另一個前臺應用已關聯到該應用(不管是通過綁定到其中一個 Service,還是通過使用其中一個內容提供程序)。

創建前臺Service方式變更

  • Android 8.0 之前

創建前臺 Service 的方式通常是先創建一個後臺 Service,然後將該 Service 推到前臺

  • Android 8.0 開始

系統不允許後臺應用創建後臺 Service。 因此,Android 8.0 引入了一種全新的方法,即 startForegroundService(),以在前臺啟動新 Service。 在系統創建 Service 後,應用有五秒的時間來調用該 Service 的 startForeground() 方法以顯示新 Service 的用戶可見通知。 如果應用在此時間限制內未調用 startForeground(),則系統將停止此 Service 並聲明此應用為 ANR。

在系統增加限制的同時,當然也提供了其他的途徑來幫助開發者解決這些限制帶來的問題。在大多數情況下,應用都可以使用 JobScheduler 克服這些限制。 這種方法允許應用安排其在未活躍運行時執行工作,不過仍能夠使系統可以在不影響用戶體驗的情況下安排這些作業。

可行的遷移方案

  • 如果處於後臺時,應用需要創建一個前臺 Service,使用 startForegroundService() 方法,而非 startService()。
  • 如果 Service 容易引起用戶注意,將其設置為前臺 Service。 例如,播放音頻的 Service 始終應為前臺 Service。 使用 startForegroundService() 方法創建 Service, 而非 startService()。
  • 尋找一種使用計劃作業實現 Service 功能的方式。 如果 Service 未在執行容易立即引起用戶注意的操作,一般情況下,都能夠使用計劃作業。
  • 在應用正常處於前臺之前,請推遲後臺工作。

今年金九銀十我花一個月的時間收錄整理了一套知識體系,如果有想法深入的系統化的去學習的,可以私信我【安卓】,我會把我收錄整理的資料都送給大家,幫助大家更快的進階。

重要的事說三遍,轉發+轉發+轉發,讓更多需要的朋友們都可以看到並且領到!

詳解Android四大組件之——服務


分享到:


相關文章: