이번 글은 기능에 대해 소개하기 전, 먼저 OS 9(Pie)을 타겟으로 만든 기능임을 알린다.
기능 및 배경 소개
우리가 어떤 프로그램이 지속 실행되어야하는 상황에 만약을 대비해 넣는 기능 중에 하나가
한번쯤은 봤을만한 꺼지면 재실행하는 기능이다.
이 기능은 보통 Watchdog(와치독, 워치독)이라 불린다.
이번에 안드로이드 셋톱박스에 맞추어 앱을 제작하게 됐는데
이 때, 꺼지지 않는 앱이 필요했고 만에 하나를 대비해 이 기능을 준비하게 되었다.
Manifest 수정하기
먼저 Foreground Service 권한을 설정해야 한다.
갑자기 무슨 Foreground Service인가 싶을 수 있다.
Background Service에서 Activity를 실행할 수는 있지만(하위 OS이기에 가능하지만)
OS에서 Service 자체를 날려버리면 다시 실행시킬 방법이 없다.
또한 Foreground Service가 OS에서 가지는 우선순위가 높으므로 웬만한 상황에서는
종료되지 않을 것이라는 기대를 가질 수 있다.
아무튼 서론이 길었는데 바로 수정해보자.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
먼저 이 두 가지 권한이 필요하다.
첫번째 권한은 일반적인 권한처럼 보이지만
두번째 권한은 앱이 Foreground Service 타입을 명시한 것이라고 할 수 있다.
그리고 우리가 사용할 Service가 Manifest에 등록될 것인데
여기에 그 타입을 권한에 명시한 것과 동일하게 설정한다.
<service
android:name=".WatchDogService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="specialUse"
android:process=":watchdogProcess" />
여기서 process는 서비스가 실행될 프로세스를 의미한다.
내가 명시한 것처럼 process값이 ":"으로 시작되면 앱 전용 새로운 프로세스가 만들어지고 여기서 서비스가 실행된다.
현재 OS(9, Pie)에서 빠진 속성이 하나 있다면 POST_NOTIFICATIONS라고 할 수 있다.
OS 13미만은 해당 권한이 자동 부여되기 때문이다.
Service 구성하기
class WatchDogService : Service() {
override fun onCreate() {
super.onCreate()
val notificationChannel = NotificationChannel("Watchdog", "Watchdog", NotificationManager.IMPORTANCE_HIGH)
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(notificationChannel)
val notificationBuilder = Notification.Builder(this, "Watchdog").apply {
setSmallIcon(R.mipmap.icon_launcher_round)
setContentTitle("Watchdog")
setContentText("Watchdog is running")
}
startForeground(100, notificationBuilder.build())
val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
CoroutineScope(Dispatchers.Main).launch {
while (true) {
println("Watchdog is still running")
if (!isForeground(activityManager)) {
val intent = Intent(this@WatchDogService, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)
}
delay(10_000L)
}
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
}
private fun isForeground(activityManager: ActivityManager) : Boolean {
val runningAppProcessInfo = activityManager.runningAppProcesses
runningAppProcessInfo.forEach { process ->
if (process.processName.equals(packageName) && process.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) return true
}
return false
}
}
먼저 서비스가 만들어지면 알림을 상태바에 만들어두고 Foreground Service를 시작한다.
그리고 AcitivityManager를 통해 현재 앱이 Foreground 상태에 있는지 확인한다.
만약 Foreground 상태에 앱이 없는 경우 MainActivity를 실행하는 코드이다.
여기서 Intent.FLAG_ACTIVITY_NEW_TASK를 설정해주었는데, 이를 지정하지 않는 경우 에러가 발생한다.
Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
액티비티 외부에서 startActivity를 호출하는 경우,
Intent.FLAG_ACTIVITY_NEW_TASK를 설정해주어야 한다는 에러이다.
결과
이렇게 되는 경우, 뒤로가기 버튼을 통해 앱을 종료하거나
OOM, 처리하지 않은 Exception 때문에 앱이 크래시가 발생하는 경우 앱을 다시 실행시킨다.
'Android' 카테고리의 다른 글
Android add library using toml (0) | 2024.11.27 |
---|---|
Android QR Code Generation (0) | 2024.11.26 |
Android Phone Number Format(자동 하이픈 추가) (0) | 2024.11.24 |
Android Compose WebView PullToRefresh 기능 구현하기 (0) | 2024.11.19 |
Android Fold Device 대응하기 (6) | 2024.11.08 |