本文主要簡單介紹一下關於service的一些基本概念,以及運用service的方法和新版系統對於軟件使用service的一些限制等。內容主要基於Andorid官方文檔。java
Service 是一種可在後臺執行長時間運行操做而不提供界面的應用組件。服務可由其餘應用組件啓動,並且即便用戶切換到其餘應用,服務仍將在後臺繼續運行。此外,組件可經過綁定到服務與之進行交互,甚至是執行進程間通訊 (IPC)。例如,服務可在後臺處理網絡事務、播放音樂,執行文件 I/O 或與內容提供程序進行交互。android
執行一些用戶能注意到的操做。例如,音頻應用會使用前臺服務來播放音頻曲目。前臺服務必須顯示通知。即便用戶中止與應用的交互,前臺服務仍會繼續運行。安全
後臺服務執行用戶不會直接注意到的操做。例如,若是應用使用某個服務來壓縮其存儲空間,則此服務一般是後臺服務。服務器
當應用組件經過調用 bindService() 綁定到服務時,服務即處於綁定狀態。綁定服務會提供客戶端-服務器接口,以便組件與服務進行交互、發送請求、接收結果,甚至是利用進程間通訊 (IPC) 跨進程執行這些操做。僅當與另外一個應用組件綁定時,綁定服務纔會運行。多個組件可同時綁定到該服務,但所有取消綁定後,該服務即會被銷燬。網絡
雖然分開歸納討論啓動服務和綁定服務,但服務可同時以這兩種方式運行,它既能夠是啓動服務(以無限期運行),亦支持綁定。惟一的問題在於實現一組回調方法:onStartCommand()(讓組件啓動服務)和 onBind()(實現服務綁定)。app
如要建立服務,必須建立 Service 的子類(或使用它的一個現有子類)。在實現中,必須重寫一些回調方法,從而處理服務生命週期的某些關鍵方面,並提供一種機制將組件綁定到服務。ide
當另外一個組件(如 Activity)請求啓動服務時,系統會經過調用 startService() 來調用此方法。執行此方法時,服務即會啓動並可在後臺無限期運行。若是實現此方法,則在服務工做完成後,需經過調用 stopSelf() 或 stopService() 來中止服務。(若是隻想提供綁定,則無需實現此方法。)性能
當另外一個組件想要與服務綁定(例如執行 RPC)時,系統會經過調用 bindService() 來調用此方法。在此方法的實現中,必須經過返回 IBinder 提供一個接口,以供客戶端用來與服務進行通訊。務必實現此方法;可是,若是並不但願被綁定,則應返回 null。ui
首次建立服務時,系統會(在調用 onStartCommand() 或 onBind() 以前)調用此方法來執行一次性設置程序。若是服務已在運行,則不會調用此方法。this
當再也不使用服務且準備將其銷燬時,系統會調用此方法。服務應經過實現此方法來清理任何資源,如線程、註冊的偵聽器、接收器等。這是服務接收的最後一個調用。
除了繼承service實現上訴的幾個重要回調以外,還須要在
<manifest ... >
...
<application ... >
<service android:name=".MyService" />
...
</application>
</manifest>
複製代碼
<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"
>
. . .
</service>
複製代碼
這會引發對 onStartCommand() 的調用,則服務會一直運行,直到其使用 stopSelf() 自行中止運行,或由其餘組件經過調用 stopService() 將其中止爲止
服務只會在該組件與其綁定時運行。當該服務與其全部組件取消綁定後,系統便會將其銷燬
若是系統在 onStartCommand() 返回後終止服務,則除非有待傳遞的掛起 Intent,不然系統不會重建服務。這是最安全的選項,能夠避免在沒必要要時以及應用可以輕鬆重啓全部未完成的做業時運行服務。
若是系統在 onStartCommand() 返回後終止服務,則其會重建服務並調用 onStartCommand(),但不會從新傳遞最後一個 Intent。相反,除非有掛起 Intent 要啓動服務,不然系統會調用包含空 Intent 的 onStartCommand()。在此狀況下,系統會傳遞這些 Intent。此常量適用於不執行命令、但無限期運行並等待做業的媒體播放器(或相似服務)。
若是系統在 onStartCommand() 返回後終止服務,則其會重建服務,並經過傳遞給服務的最後一個 Intent 調用 onStartCommand()。全部掛起 Intent 均依次傳遞。此常量適用於主動執行應當即恢復的做業(例以下載文件)的服務。
上面已經提到,建立服務除了繼承Service以外,還能夠直接使用它的一個現有子類,那麼這個之類是誰呢?就是下面這位了...
關於二者,他們適應不一樣的應用場景來實現服務:
這是適用於全部服務的基類。擴展此類時,必須建立用於執行全部服務工做的新線程,由於服務默認使用應用的主線程,這會下降應用正在運行的任何 Activity 的性能。
這是 Service 的子類,其使用工做線程逐一處理全部啓動請求。若是不要求服務同時處理多個請求,此類爲最佳選擇。實現 onHandleIntent(),該方法會接收每一個啓動請求的 Intent,以便執行後臺工做。
@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);
}
複製代碼
至此,咱們已經知道如何建立服務,那麼建立好的服務又應如何進行開啓、中止和綁定呢?這就是接下來咱們要解決的問題...
能夠經過將 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() 啓動的服務那樣,以相同方式中止綁定服務。
前臺服務是用戶主動意識到的一種服務,所以在內存不足時,系統也不會考慮將其終止。前臺服務必須爲狀態欄提供通知,將其放在運行中的標題下方。這意味着除非將服務中止或從前臺移除,不然不能清除該通知。
在使用前臺服務時,應注意如下事項:
只有當應用執行的任務需供用戶查看(即便該任務未直接與應用交互)時,才應使用前臺服務。所以,前臺服務必須顯示優先級爲 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生命週期圖示:
關於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 更容易引發用戶注意。
那麼什麼是空閒狀態呢?
處於前臺時,應用能夠自由建立和運行前臺與後臺 Service。 進入後臺時,在一個持續數分鐘的時間窗內,應用仍能夠建立和使用 Service。 在該時間窗結束後,應用將被視爲處於空閒狀態。 此時,系統將中止應用的後臺 Service,就像應用已經調用 Service 的 Service.stopSelf() 方法同樣。
建立前臺 Service 的方式一般是先建立一個後臺 Service,而後將該 Service 推到前臺
系統不容許後臺應用建立後臺 Service。 所以,Android 8.0 引入了一種全新的方法,即 startForegroundService(),以在前臺啓動新 Service。 在系統建立 Service 後,應用有五秒的時間來調用該 Service 的 startForeground() 方法以顯示新 Service 的用戶可見通知。 若是應用在此時間限制內未調用 startForeground(),則系統將中止此 Service 並聲明此應用爲 ANR。
在系統增長限制的同時,固然也提供了其餘的途徑來幫助開發者解決這些限制帶來的問題。在大多數狀況下,應用均可以使用 JobScheduler 克服這些限制。 這種方法容許應用安排其在未活躍運行時執行工做,不過仍可以使系統能夠在不影響用戶體驗的狀況下安排這些做業。