Android四大元件之S
1.什麼是Service?
Service即服務,服務就是無使用者介面也可以執行在後臺的元件。與Activity不同的是:一直在後臺執行,沒有使用者介面。
2.使用Service還是新執行緒
前提:需要在主執行緒外執行耗時工作
- 需要與使用者互動時,建立新執行緒執行。例:Activity執行的同時播放音樂。
- 無需與使用者進行互動,建立Service執行。
注意:Service執行在主線中,如果進行耗時操作則需開啟子執行緒執行操作。
3.Service生命週期
引用Google官方詳解圖:
注意:與 Activity 生命週期回撥方法不同,您不需要呼叫這些回撥方法的超類實現。
具體方法:
- onCreate()
- onStartCommand() 當元件通過startService()啟動服務時,呼叫此方法。一旦執行此方法,服務即會啟動並可在後臺無限期執行。如需停止則呼叫sotpSelf()或者stopService().多次start多次回撥。
- onBind() 當元件通過呼叫bindService()與服務繫結時,回撥此方法。如果不允許繫結則返回null.
- onUnbind() 當Service上繫結的所有客戶端都斷開時將會回撥該方法。
- onDestroy() 當服務不再使用且將被銷燬時,系統將呼叫此方法。服務應該實現此方法來清理所有資源,如執行緒、註冊的偵聽器、接收器等。 這是服務接收的最後一個呼叫。
4.Service使用
- 定義一個繼承Service的子類
- AndroidManifest.xml中宣告
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
android:name
屬性是唯一必需的屬性,用於指定服務的類名
android:exported
屬性並將其設定為 “false”,確保服務僅適用於您的應用
也可以在< Service>標籤中定義< intent-fliter>,不過在Android 5.0之後只能顯式的啟動服務
執行Service的兩種方式:
- 通過Context.startService()方法,與啟動者無關聯,單獨的生命週期
- 通過Context.bindService()方法,與啟動這相關聯,關聯著退出,隨之終止
使用:
MyService.kt
package com.wdl.crazyandroiddemo.service
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log
class MyService : Service() {
private val binder:Binder = MyBinder()
private class MyBinder : Binder() {
fun count() = 5
}
override fun onBind(intent: Intent): IBinder? {
Log.e("wdl", "------onBind()-----")
return binder
}
override fun onCreate() {
super.onCreate()
Log.e("wdl", "------onCreate()-----")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.e("wdl", "------onStartCommand()-----")
return super.onStartCommand(intent, flags, startId)
}
override fun onUnbind(intent: Intent?): Boolean {
Log.e("wdl", "------onUnbind()-----")
return true
}
override fun onRebind(intent: Intent?) {
super.onRebind(intent)
Log.e("wdl", "------onRebind()-----")
}
override fun onDestroy() {
super.onDestroy()
Log.e("wdl", "------onDestroy()-----")
}
}
測試demo
package com.wdl.crazyandroiddemo
import android.app.Service
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import com.wdl.crazyandroiddemo.service.MyService
import kotlinx.android.synthetic.main.activity_service.*
class ServiceActivity : AppCompatActivity() {
private val conn = object :ServiceConnection{
override fun onServiceDisconnected(name: ComponentName?) {
Log.e("wdl","異常斷開連線-------")
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.e("wdl","連線-------")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_service)
mStart.setOnClickListener {
val intent = Intent(this,MyService::class.java)
startService(intent)
}
mStop.setOnClickListener {
val intent = Intent(this,MyService::class.java)
stopService(intent)
}
mBindService.setOnClickListener {
val intent = Intent(this,MyService::class.java)
bindService(intent,conn,Service.BIND_AUTO_CREATE)
}
mUnBindService.setOnClickListener {
unbindService(conn)
}
}
}
點選第一個和第三個按鈕,啟動Service和停止Service。
檢視日誌:
startService後多次重複點選,只執行onStartCommand()方法。
bindService後通過unBindService解除繫結。
通過第二種方法繫結服務時,完整的方法為bindService(Intent intent,ServiceConnection conn,int flags)
對應引數為:
- intent 指定的service
- conn ServiceConnection 物件,用於監聽訪問者與Service之間的連線情況。連線
異常時
,回撥onServiceDisconnected(name: ComponentName?)
- flag 指定繫結時是否自動建立Service(如果還未建立),0不自動建立,BIND_AUTO_CREATE自動建立
多個客戶端可以同時繫結到服務。客戶端完成與服務的互動後,會呼叫 unbindService(),取消繫結。一旦沒有客戶端繫結到該服務,系統就會銷燬它。
要建立繫結服務,必須實現 onBind() 回撥方法以返回 IBinder,您可以繫結到已經使用
startService()
啟動的服務。例如,可以通過使用 Intent(標識要播放的音樂)呼叫 startService() 來啟動後臺音樂服務。隨後,可能在使用者需要稍加控制播放器或獲取有關當前播放歌曲的資訊時,Activity 可以通過呼叫bindService()
繫結到服務。在這種情況下,除非所有客戶端均取消繫結,否則stopService() 或 stopSelf()
不會實際停止服務。
5.擴充套件
- ·
Service
這是適用於所有服務的基類。擴充套件此類時,必須建立一個用於執行所有服務工作的新執行緒
,因為預設情況下,服務將使用應用的主執行緒,這會降低應用正在執行的所有 Activity 的效能。 IntentService
這是 Service 的子類,它使用工作執行緒
逐一處理所有啟動請求。如果您不要求服務同時處理多個請求,這是最好的選擇。 您只需實現onHandleIntent()
方法即可,該方法會接收每個啟動請求的 Intent,使您能夠執行後臺工作。
IntentService特性:
- 建立
預設的工作執行緒
,用於在應用的主執行緒外
執行傳遞給 onStartCommand() 的所有 Intent。 - 建立
工作佇列
,用於將 Intent 逐一傳遞給onHandleIntent()
實現 - 在處理完所有啟動請求後
自動停止服務
,因此您永遠不必呼叫 stopSelf()。 - 提供 onBind() 的預設實現(返回 null)。
- 供 onStartCommand() 的預設實現,可將 Intent 依次傳送到工作佇列和 onHandleIntent() 實現。
案例:
package com.wdl.crazyandroiddemo.service
import android.app.IntentService
import android.content.Intent
import android.util.Log
/**
* An [IntentService] subclass for handling asynchronous task requests in
* a service on a separate handler thread.
* TODO: Customize class - update intent actions and extra parameters.
*/
class MyIntentService : IntentService("MyIntentService") {
override fun onHandleIntent(intent: Intent?) {
//直接執行耗時操作
val endTime = System.currentTimeMillis()+20*1000
Log.e("wdl","onHandleIntent")
while (System.currentTimeMillis()<endTime){
synchronized(this){
Thread.sleep(endTime-System.currentTimeMillis())
Log.e("wdl","synchronized")
}
}
}
}
onStartCommand()
方法必須返回整型數,用於描述系統應該如何在服務終止的情況下繼續執行服務
- START_NOT_STICKY
如果系統在 onStartCommand() 返回後終止服務,則除非有掛起 Intent 要傳遞,否則系統不會重建服務。這是最安全的選項,可以避免在不必要時以及應用能夠輕鬆重啟所有未完成的作業時執行服務。 - START_STICKY
如果系統在 onStartCommand() 返回後終止服務,則會重建服務並呼叫 onStartCommand(),但不會重新傳遞最後一個 Intent。相反,除非有掛起 Intent 要啟動服務(在這種情況下,將傳遞這些 Intent ),否則系統會通過空 Intent 呼叫 onStartCommand()。這適用於不執行命令、但無限期執行
並等待作業的媒體播放器(或類似服務)。 - START_REDELIVER_INTENT
如果系統在 onStartCommand() 返回後終止服務,則會重建服務,並通過傳遞給服務的最後一個 Intent 呼叫 onStartCommand()。任何掛起 Intent 均依次傳遞。這適用於主動執行應該立即恢復的作業
(例如下載檔案)的服務。
如果服務同時處理
多個 onStartCommand()
請求,則您不應在處理完一個啟動請求之後停止服務,因為您可能已經收到了新的啟動請求(在第一個請求結束時停止服務會終止第二個請求)。為了避免這一問題,您可以使用stopSelf(int)
確保服務停止請求始終基於最近的啟動請求。也就說,在呼叫 stopSelf(int) 時,傳遞與停止請求的 ID 對應的啟動請求的 ID(傳遞給 onStartCommand() 的 startId)。然後,如果在您能夠呼叫 stopSelf(int) 之前服務收到了新的啟動請求,ID 就不匹配,服務也就不會停止。
6.前臺服務
如果希望服務一直保持執行狀態,而不會因為系統記憶體不足導致被回收,就可以考慮使用前臺服務。前臺服務與後臺服務最大的區別是,他會有一個正在執行的圖示在系統的狀態列顯示,下拉可以看到更加詳細的資訊。
override fun onCreate() {
super.onCreate()
Log.e("wdl", "------onCreate()-----")
val intent = Intent(this,ServiceActivity::class.java)
val pi = PendingIntent.getActivity(this,0,intent,0)
val notification = Notification(R.drawable.ic_launcher_background,"hello",System.currentTimeMillis())
notification.contentIntent = pi
startForeground(1,notification)
}
通過使用
- startForeground(1,notification) 開啟前臺服務,第一個引數不能為0
- stopForeground(boolean) 引數代表是否將通知從狀態列中移除