Service
안드로이드 4대 컴포넌트 중 하나이며 사용자에게 인터페이스를 제공하지 않고 백그라운드에서 오래 실행되는 작업을 수행할 수 있는 애플리케이션 구성요소
다른 애플리케이션 구성 요소가 서비스를 시작할 수 있고, 다른 앱을 전환하더라도 백그라운드에서 계속 실행된다.
또한 앱 구성요소와 서비스를 바인딩하여 서비스와 상호작용 할 수 있고, 프로세스 간 통신(IPC)도 실행할 수 있다.
종류
Background Service
사용자에게 보이지 않는 백그라운드 작업을 수행하며, 시스템 리소스가 부족할 경우 강제 종료될 수 있다.
API 25 이상부터는 앱이 Foreground에 있지 않을 때 백그라운드 서비스를 강제로 종료시킨다.
Foreground Service
만약 서비스가 백그라운드에서 계속 돌게 된다면, 안드로이드 시스템에 의해서 무작위로 종료당할 수 있다.
실시간으로 사용 중인 다른 앱에서 더 많은 자원(메모리)을 필요로 한다면, 오래 도는 서비스는 결코 안전하지 못하다.
Foreground Service는 활성화된 액티비티와 동일한 우선순위를 가지므로 시스템에 메모리가 부족하더라도 강제 종료될 확률이 낮다.
하지만 사용자가 서비스의 동작을 인지할 수 있도록 반드시 알림(Notify)을 제공해야 한다.
시스템은 이로 인해 사용자가 서비스를 사용 중임으로 인지하고 종료하지 않는 것이다.
Bound Service
안드로이드의 구성요소가 서비스에 바인딩하여 상호작용 할 수 있다.
IBinder라는 인터페이스로 Client - Server 관계처럼 서비스와 상호작용하며 여러 프로세스에 걸쳐 프로세스 간 통신(IPC)을 수행할 수도 있다.
여러 개의 구성 요소가 서비스에 한꺼번에 바인딩될 수 있지만, 모든 구성요소에서 바인딩이 해제되면 해당 서비스는 소멸된다.
LifeCycle
onCreate
서비스가 처음 생성되었을 경우 onStartCommand 혹은 onBind가 호출되기 전에 호출된다.
서비스가 이미 실행중일 경우는 이 메소드는 호출되지 않는다.
onStartCommand
startService()로 실행되었을 경우 onCreate 이후 실행된다.
이 함수가 호출되면 백그라운드에서 무한히 실행되므로, 반드시 stopSelf() 혹은 stopService()로 서비스를 종료시켜야 한다. (바인딩만 할 경우 이 함수를 사용하지 않아도 된다.)
onStartCommand는 return 값이 존재하는데 아래와 같다.
START_NOT_STICKY | 서비스를 명시적으로 다시 시작할 때 까지 만들지 않는다. 서비스가 불필요하게 여러개 생성되는 것을 막을수 있는 방법이다. |
START_STICKY | 서비스를 다시 만들지만 마지막 Intent를 onStartCommand의 인자로 다시 전달하지 않는다. 이는 일단 계속 살아있어야되지만 별다른 동작이 필요하지 않은 음악앱같은 서비스에 적절 |
START_REDELIVER_INTENT | 서비스를 다시 생성하고 마지막 Intent를 onStartCommand의 인자로 다시 전달한다. 즉각적인 반응이 필요한 파일 다운로드 서비스 같은 곳에 적합 |
onBind
안드로이드의 구성요소가 서비스에 바인딩하고자 하는 경우에 호출 / bindService()
이 함수의 구현체는 IBinder를 return 하여 클라이언트가 서비스와 통신을 주고받기 위해 사용할 인터페이스를 제공해야 된다.
항상 이 함수를 구현해야 하며 바인딩을 원하지 않을 시 null을 반환
차이 확인
<application
...
<service android:name=".MyBackgroundService"/>
<service android:name=".MyForegroundService"/>
<service android:name=".MyBoundService"/>
</application>
Background
class MyBackgroundService : Service() {
private var job : Job? = null
private var count = 0
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
job = CoroutineScope(Dispatchers.Default).launch {
while (isActive){
Log.e("Service Test", "BackgroundService 실행중")
Log.e("Service Test", "$count")
try {
delay(2000)
count++
} catch (e: Exception){
e.printStackTrace()
}
}
}
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
Log.e("Service Test", "onDestroy : job cancel")
}
}
val backgroundService = Intent(this@MainActivity, MyBackgroundService::class.java)
startService(backgroundService)
//stop
stopService(backgroundService)
Foreground
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
class MyForegroundService : Service() {
private var job : Job? = null
private var count = 0
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
job = CoroutineScope(Dispatchers.Default).launch {
while (isActive){
Log.e("Service Test", "ForegroundService 실행중")
Log.e("Service Test", "$count")
try {
delay(2000)
count++
} catch (e: Exception){
e.printStackTrace()
}
}
}
initNotification()
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
Log.e("Service Test", "onDestroy : job cancel")
}
private fun initNotification(){
val channel = NotificationChannel(
"channel-one",
"Channel-One",
NotificationManager.IMPORTANCE_LOW)
.apply {
setShowBadge(false)
}
getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
val builder = Notification.Builder(this@MyForegroundService, "channel-one")
.setContentTitle("ForegroundService")
.setContentText("서비스 실행중")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setOngoing(true)
startForeground(1, builder.build())
}
}
val foregroundService = Intent(this@MainActivity, MyForegroundService::class.java)
startService(foregroundService)
//stop
stopService(foregroundService)
Bound
class MyBoundService : Service(){
private val mBinder = MyBinder()
private var count = 0
private var job = CoroutineScope(Dispatchers.Default).launch {
while (isActive){
Log.e("Service Test", "BoundService 실행중")
Log.e("Service Test", "$count")
try {
delay(2000)
count++
} catch (e: Exception){
e.printStackTrace()
}
}
}
inner class MyBinder : Binder() {
fun getService() : MyBoundService {
return this@MyBoundService
}
}
override fun onBind(p0: Intent?): IBinder = mBinder
override fun onCreate() {
job.start()
}
//연결한 안드로이드 컴포넌트에서 사용할 함수
fun getCount() = count
override fun onDestroy() {
super.onDestroy()
job.cancel()
Log.e("Service Test", "onDestroy : job cancel")
}
}
private lateinit var mBoundService : MyBoundService
var isService = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as MyBoundService.MyBinder
mBoundService = binder.getService()
isService = true
Log.e("BoundService Test", "Connected")
}
override fun onServiceDisconnected(name: ComponentName?) {
isService = false
}
}
Activity, Fragment 등등 안드로이드 요소와 서비스를 연결할 Connection 지정
bindService() 메서드를 통해 시스템에 전달하면 서비스와 연결할 수 있음
onServiceConnected : 서비스가 연결되면 호출
onServiceDisconnected : 정상적 연결 해제되었을 때는 호출되지 않고, 비정상적으로 서비스가 종료되었을 때만 호출
변수 isService : onServiceDisconnected 구조 때문에 서비스 연결 상태를 확인하는 로직이 필요해 선언
bindService(intent, connection, Context.BIND_AUTO_CREATE)
//BIND_AUTO_CREATE : 서비스가 생성되어 있지 않으면 생성 후 바인딩을 하고 생성되어 있으면 바로 바인딩
//Unbind
if (isService){
unbindService(connection)
isService = false
}
MyBoundService에 만든 함수 사용
if (isService){
Toast.makeText(this@MainActivity, "BoundService Count: ${mBoundService.getCount()}", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this@MainActivity, "BoundService가 연결되지 않았습니다.", Toast.LENGTH_SHORT).show()
}
Service
https://velog.io/@kingdo/Android-Service란
https://parade621.tistory.com/52
https://seosh817.tistory.com/115
알림 없이 Foreground 이용 참고
https://stickode.tistory.com/749
ActivityManager getRunningServices Deprecated
https://copyprogramming.com/howto/java-kotlin-check-if-service-is-running
'Android > Reference' 카테고리의 다른 글
AlarmManager (0) | 2023.12.12 |
---|---|
Notification (0) | 2023.12.09 |
DataStore / Preference, Proto (0) | 2023.12.04 |
BottomSheet (Persistent, Modal) (0) | 2022.05.02 |
RecyclerView Item에 Animation 주기 (0) | 2022.04.18 |