vault backup: 2025-02-10 17:27:57
This commit is contained in:
205
20.01. Android/Service.md
Normal file
205
20.01. Android/Service.md
Normal file
@@ -0,0 +1,205 @@
|
||||
## Service的生命週期
|
||||
![[Pasted image 20220307103552.png]]
|
||||
|
||||
## Service的啟動方式
|
||||
Service由`startService()`啟動之後,便獨立動作,啟動者(例如某個activity)無法取得Service的intance,也無法呼叫Service的API。Service可以被多次呼叫`startService()`,但是只要一旦`stopService()`被呼叫了,Service就會結束。所以需要確保Service的管理者是誰,統一管理者來呼叫`startService()`與`stopService()`才不會造成混亂。
|
||||
|
||||
`bindService()`則是像是典型的Server-Client架構,第一次`bindService()`的時候會建立Service的instance,然後可以多次`bindService()`與`unbindService()`,當Service沒也任何人跟它"Bind"的時候,Service才會結束。
|
||||
|
||||
### startService
|
||||
要建立自己的Service,需要繼承`Service()`類別,然後複寫4個成員函式:
|
||||
```kotlin
|
||||
class MyService : Service() {
|
||||
|
||||
override fun onCreate() {
|
||||
Log.i("Awin","onCreate - Thread ID = " + Thread.currentThread().id)
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.i("Awin", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id)
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder? {
|
||||
Log.i("Awin", "onBind - Thread ID = " + Thread.currentThread().id)
|
||||
return null // 因為目前沒有要支援bindService(),所以這裡直接return null
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i("Awin", "onDestroy - Thread ID = " + Thread.currentThread().id)
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
假設現在由MainActivity呼叫`startService()`,因為是第一次呼叫,所以MyService的callback被呼叫的順序是:`onCreate()` -> `onStartCommand()`。
|
||||
第二次之後的呼叫就只會執行`onStartCommand()`。
|
||||
當MainActivity呼叫`stopService()`時,則會執行MyService的`onDestroy()`。
|
||||
|
||||
所以,必須將「長期任務」開個 Thread 並且執行在 `onStartCommand()` 方法內。
|
||||
|
||||
Client端的code:
|
||||
```kotlin
|
||||
val intent = Intent(this, ServiceDemo.class)
|
||||
startService(intent)
|
||||
```
|
||||
|
||||
### bindService
|
||||
使用`bindService()`來建立Service的一個好處就是可以取得Service的instance,然後藉由這個instance來使用Service的API。
|
||||
要使用`bindService()`的話,Service類別必須實做`onBind()`與`onUnbind()`至兩個override function。
|
||||
也必須提供一個Binder class(繼承自`Binder()`),來讓client呼叫。例:
|
||||
```kotlin
|
||||
class MyService : Service() {
|
||||
|
||||
//client 可以通过Binder获取Service实例
|
||||
inner class MyBinder : Binder() {
|
||||
val service: MyService
|
||||
get() = this@MyService
|
||||
}
|
||||
|
||||
//通过binder实现调用者client与Service之间的通信
|
||||
private val binder = MyBinder()
|
||||
|
||||
private val generator: Random = Random()
|
||||
|
||||
override fun onCreate() {
|
||||
Log.i("xiao", "MyService - onCreate - Thread = " + Thread.currentThread().name)
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param intent 啟動時,啟動組件傳遞過來的Intent,如Activity可利用Intent封裝所需要的參數並傳遞給Service
|
||||
* @param flags 表示啟動請求時是否有額外數據,可選值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY
|
||||
* 0: 在正常創建Service的情況下,onStartCommand傳入的flags為0。
|
||||
*
|
||||
* START_FLAG_REDELIVERY:
|
||||
* 這個值代表了onStartCommand()方法的返回值為 START_REDELIVER_INTENT,
|
||||
* 而且在上一次服務被殺死前會去調用stopSelf()方法停止服務。
|
||||
* 其中START_REDELIVER_INTENT意味著當Service因記憶體不足而被系統kill後,
|
||||
* 則會重建服務,並透過傳遞給服務的最後一個 Intent調用 onStartCommand(),此時Intent時有值的。
|
||||
*
|
||||
* START_FLAG_RETRY
|
||||
* 該flag代表當onStartCommand()調用後一直沒有返回值時,會嘗試重新去調用onStartCommand()。
|
||||
*
|
||||
* @param startId 指明當前服務的唯一ID,與stopSelfResult(int startId)配合使用,stopSelfResult()可以更安全地根據ID停止服務。
|
||||
*
|
||||
* @return
|
||||
* START_STICKY:
|
||||
* 當Service因記憶體不足而被系統kill後,一段時間後記憶體再次空閒時,
|
||||
* 系統將會嘗試重新創建此Service,一旦創建成功後將回調onStartCommand方法,
|
||||
* 但其中的Intent將是null,除非有掛起的Intent,如pendingintent,
|
||||
* 這個狀態下比較適用於不執行命令、但無限期運行並等待作業的媒體播放器或類似服務
|
||||
*
|
||||
*
|
||||
* START_NOT_STICKY:
|
||||
* 當Service因記憶體不足而被系統kill後,即使系統記憶體再次空閒時,
|
||||
* 系統也不會嘗試重新創建此Service。除非程序中再次調用startService啟動此Service,
|
||||
* 這是最安全的選項,可以避免在不必要時以及應用能夠輕鬆重啟所有未完成的作業時運行服務。
|
||||
*
|
||||
* START_REDELIVER_INTENT:
|
||||
* 當Service因記憶體不足而被系統kill後,則會重建服務,
|
||||
* 並透過傳遞給服務的最後一個 Intent 調用 onStartCommand(),任何掛起 Intent均依次傳遞。
|
||||
* 與START_STICKY不同的是,其中的傳遞的Intent將是非空,是最後一次調用startService中的intent。
|
||||
* 這個值適用於主動執行應該立即恢復的作業(例如下載文件)的服務。
|
||||
*/
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.i("xiao", "MyService - onStartCommand - startId = $startId, Thread = " + Thread.currentThread().name)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder{
|
||||
Log.i("xiao", "MyService - onBind - Thread = " + Thread.currentThread().name)
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent): Boolean {
|
||||
Log.i("xiao", "MyService - onUnbind - from = " + intent.getStringExtra("from"))
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i("xiao", "MyService - onDestroy - Thread = " + Thread.currentThread().name)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
//getRandomNumber是Service暴露出去供client调用的公共方法
|
||||
fun getRandomNumber(): Int {
|
||||
return generator.nextInt()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Client端要做的事:
|
||||
1. 建立ServiceConnection類型實例,並覆載`onServiceConnected()`方法和`onServiceDisconnected()`方法。
|
||||
2. 當執行到onServiceConnected callback時,可通過IBinder instance得到Service的instance,這樣可實現client與Service的連接。
|
||||
3. onServiceDisconnected callback被執行時,表示client與Service已經斷開,在此可以寫一些斷開後需要做的處理。
|
||||
```kotlin
|
||||
class AActivity : AppCompatActivity() {
|
||||
|
||||
private var service: MyService? = null
|
||||
private var isBind = false
|
||||
|
||||
private var conn = object : ServiceConnection{
|
||||
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
|
||||
isBind = true
|
||||
val myBinder = p1 as MyService.MyBinder
|
||||
service = myBinder.service
|
||||
Log.i("xiao", "ActivityA - onServiceConnected")
|
||||
val num = service!!.getRandomNumber()
|
||||
Log.i("xiao", "ActivityA - getRandomNumber = $num");
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(p0: ComponentName?) {
|
||||
isBind = false
|
||||
Log.i("xiao", "ActivityA - onServiceDisconnected")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_a)
|
||||
|
||||
Log.i("xiao", "ActivityA - onCreate - Thread = " + Thread.currentThread().name)
|
||||
|
||||
btn_bind_service_a.setOnClickListener {
|
||||
val intent = Intent(this,MyService::class.java)
|
||||
intent.putExtra("from","ActivityA")
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 执行 bindService");
|
||||
bindService(intent, conn, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
btn_unbind_service_a.setOnClickListener {
|
||||
if(isBind){
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 执行 unbindService");
|
||||
unbindService(conn)
|
||||
}
|
||||
}
|
||||
btn_a_start_b.setOnClickListener {
|
||||
val intent = Intent(this,BActivity::class.java)
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 启动 ActivityB");
|
||||
startActivity(intent)
|
||||
}
|
||||
btn_finish_a.setOnClickListener {
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 执行 finish");
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Log.i("xiao", "ActivityA - onDestroy")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 參考
|
||||
1. [如何使用Service(kotlin)](https://givemepass.blogspot.com/2015/10/service.html)
|
||||
2. [Android kotlin service使用简析 - 簡書](https://www.jianshu.com/p/f9712b470b42)
|
||||
3. [《Android》『Service』- 背景執行服務的基本用法 - 賽肥膩膩の娛樂生活誌](https://xnfood.com.tw/android-service/#skill_02)
|
||||
Reference in New Issue
Block a user