AlarmManager -> Service

오븐·2024년 10월 22일

안드로이드

목록 보기
3/9

AlarmManager을 통해 서비스를 작동시켜보자!

우선 알람을 Scheduler를 통해 보낸다.

class AndroidAlarmScheduler (private val context: Context): AlarmScheduler {
    private val alarmManager = context.getSystemService(AlarmManager::class.java)

    @SuppressLint("ScheduleExactAlarm") // 1
    override fun schedule(item: AlarmItem) {
        val intent = Intent(context, AlarmReceiver::class.java).apply {
            putExtra("EXTRA_MESSAGE", item.message)
        }

        alarmManager.setExactAndAllowWhileIdle(
            AlarmManager.RTC_WAKEUP,
            item.time.toEpochSecond() * 1000,
            PendingIntent.getBroadcast(
                context,
                item.hashCode(),
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE // 2, 3
            )
        )

    }
  1. Lint는 생길 수 있는 버그나 최적화를 체크하는 툴이다. SuppresLint는 해당 주석이 달린 컴포넌트에 대한 경고를 무시한다. minSdkVersion 이후의 API를 사용할 때 올라오는 경고를 무시하기 위해 사용한다.
  2. PendingIntent.FLAG_UPDATE_CURRENT: 보류 중인 Intent가 존재할 경우 extra 데이터는 새 intent의 extra 데이터로 업데이트 해야한다.
  3. PendingIntent.FLAG_IMMUTABLE: 생성된 PendingIntent는 수정할 수 없다. 추후에 send 메서드에 추가된 Intent 파라미터는 무시된다. (Android 12부터 PendingIntent는 반드시 해당 플래그를 사용해야 한다.)

이제 이걸 BroadcastReceiver가 받는다.

class AlarmReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        val serviceIntent = Intent(context, AlarmService::class.java) // 1
        if (intent != null){
            serviceIntent.putExtras(intent)
        }

        val versionNum = Build.VERSION.SDK_INT

        if(versionNum >= Build.VERSION_CODES.S){ // 2
            val pendingIntent = PendingIntent.getForegroundService(context, 1, serviceIntent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
            pendingIntent.send()
        } else if (versionNum >= Build.VERSION_CODES.O){ // 3
            context?.startForegroundService(serviceIntent)
        } else { // 4
            context?.startService(serviceIntent)
        }
    }

}
  1. 시작할 서비스 클래스를 정보를 Intent에 담아, PendingIntent 담거나 해서 포그라운드 서비스를 시작한다.
  2. Android 12 (API level 31) 이상은 PendingIntent를 통해 서비스를 시작한다.

    Android 12부터는 알림 트램폴린으로 사용되는 서비스 또는 브로드캐스트 리시버에서 액티비티를 시작할 수 없다. 즉 이 안에서 startActivity를 호출할 수 없다. 대신 PendingIntent를 사용해야 한다.
    출처: https://developer.android.com/reference/android/content/Context#startForegroundService(android.content.Intent)

  3. API level 26 ~ 30은 context.startForegroundService를 통해 시작한다. 이 API는 26부터 추가됐다.

    26 이상부터 해당하는 이야기: 오래 사용하는 백그라운드의 경우 포그라운드 서비스 - 알림을 표시해야 한다고 한다. 포그라운드 서비스 자체가 상태바 알림을 보여주는 용도이기에 notification을 써야 한다고. 여기서는 포그라운드 서비스 중 Media를 사용한다.

  4. API level 26 미만은 그냥 startService를 통해 시작한다. 26 이전에는 포그라운드 서비스 권한 없이 자유롭게 백그라운드 서비스를 시작할 수 있다고 한다.

API level 30 ~ 33은 카메라, 마이크를 사용할 경우 manifest에 작성해야하고,
API level 34 이상은 모든 포그라운드 서비스를 maniftest에 등록해야 한다.

이제 이를 통해 Service를 시작한다.

class AlarmService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (intent == null){
            println("No Alarm Triggered: no Intent")
            stopSelf()
            return START_NOT_STICKY
        }

        val notification = createNotification()
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){ // 1
                startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
            } else { // 2
                startForeground(1, notification)
            }
        } catch (e: Exception) {
            e.printStackTrace()
            stopSelf()
            return START_NOT_STICKY
        }

        val message = intent.getStringExtra("EXTRA_MESSAGE")
        println("Alarm triggered: ${message}")

        return START_STICKY
    }
...
  1. API level 29에 추가된 startForeground. 포그라운드 서비스 타입을 받는다. 이 파라미터는 매니페스트 속성에 작성된 플래그 중 하나여야 한다. 아니면 에러가 던져진다.
  2. 그 이하는 기존의 startForeground로 포그라운드 서비스를 실행한다.
profile
하루에 한번 정권 찌르기

0개의 댓글