오늘도 삽질중

android Oreo 이상 앱 종료시점 본문

안드로이드

android Oreo 이상 앱 종료시점

Choi3950 2020. 12. 18. 20:09
반응형

Android Oreo 이상 앱 종료시점 캐치하기



해당 블로그는 다음과 같은 분들에게 도움이 될꺼라 판단합니다.

앱 종료시점(슬라이드 종료) 를 캐치하는데 Oreo 이상은 startForegroundService를 실행하는건 알고 있다.

문제는 startForegroundService 는 노티피케이션 ui가 생기는데 이걸 제거하고 싶다.


또는


간단한 백그라운드 서비스를 구현하려고 할 때

startForegroundService를 사용하여 편법으로 백그라운드에서 서비스를 돌릴수 있을까?




아마 이 블로그를 들어온 사람들은 거의 비슷하게 생긴 예제들을 봤을거라고 생각합니다.

서비스 시작시 OS 버전을 체크하여 서비스를 실행한다.


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(Intent(this, TerminateService::class.java))
} else {
startService(Intent(this, TerminateService::class.java))
}


하지만 저처럼 위와 같은 문제로 고민하시는 분들이 있을꺼 같아 여러 블로그를 찾아보고 간단하면서 원하는 방식으로

해결할 수 있었습니다.



https://vision-ary.tistory.com/16

해당블로그의 도움을 받았습니다. 한번 읽어보시면 좋을 거 같습니다.



위와 같은 문제를 해결한 방법의 핵심은 

Oreo 미만은 기존 그대로 startService를 호출하지만

Oreo 이상 버전은 startForegroundService를 호출하고 그 안에서 startService를 실행한다 입니다.



바로 코드로 넘어가겠습니다.


class TerminateService : Service() {
override fun onCreate() {
super.onCreate()
}

override fun onDestroy() {
super.onDestroy()
}

override fun onBind(intent: Intent): IBinder? {
return null
}

override fun onTaskRemoved(rootIntent: Intent) {
/**
* 해당 부분에서 앱 종료시점을 캐치할 수 있다.
*/
stopSelf()
}

override fun onLowMemory() {
super.onLowMemory()
}

override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
}

기본적인 Service()를 상속받은 Class입니다. 실제 동작해야 될 코드가 기술되어 있을겁니다.




그 다음 하나의 Service()를 상속받은 또 다른 Class를 작성합니다.

/**
* 오레오 버전 이상 임시로 실행하는 서비스
* 해당 서비스 내에서 실제로 동작하는 서비스를 실행시킨다.
*/
class TerminateServiceOreo : Service() {
override fun onCreate() {
super.onCreate()
}

override fun onDestroy() {
super.onDestroy()
}

override fun onBind(intent: Intent): IBinder? {
return null
}

override fun onTaskRemoved(rootIntent: Intent) {
}

override fun onLowMemory() {
super.onLowMemory()
}

override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("Background", "Service", NotificationManager.IMPORTANCE_LOW)
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
val notification = NotificationCompat.Builder(this, channel.id).build()
startForeground(1, notification)

val realService = Intent(this , TerminateService::class.java)
startService(realService)

stopForeground(true)
stopSelf()

}

return START_NOT_STICKY
}
}

Oreo 이상에서 실행시킬 서비스입니다.

START_NOT_STICKY로 서비스가 종료되어도 다시 실행하지 않으며, 

stopForeground(true) 로 노티피케이션 ui를 제거합니다.

그 다음 실제로 구현에 필요한 서비스를 startService로 실행합니다.



이제 해당 서비스를 버전별로 다르게 실행합니다.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(Intent(this, TerminateServiceOreo::class.java))
} else {
startService(Intent(this, TerminateService::class.java))
}


Oreo 이상은 TerminateServiceOreo 를 실행하고,

그 미만버전은 TerminateService를 실행합니다.



마지막으로 메니페스트에 서비스를 추가해줍니다.

<service android:name=".service.TerminateService" android:stopWithTask="false"/>
<service android:name=".service.TerminateServiceOreo" android:stopWithTask="true"/>





참고로 Java에서 kotlin으로 변환하게 되면 아래처럼 onStartCommand로 변환됩니다.

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
return START_STICKY
}

이렇게 그대로 쓰시게 되면....

Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter intent
에러를 보게 됩니다. 그러므로 꼭 Intent 옆에 ? 를 붙여서 사용해주세요.

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}

반응형
Comments