안드로이드 AlarmManager

오븐·2024년 10월 2일

안드로이드

목록 보기
6/9

해당 글은 이 영상을 보고 공부한 걸 기록했다.

WorkManager vs AlarmManager

AlarmManager

  • 특정 코드를 특정 시간에 실행하기 위함
  • 유저가 알람을 세팅함. 또는 인지함
  • 예: 달력, 알람

WorkManager

  • 지속적으로 백그라운드 작업을 실행함
  • 유저는 해당 매니저를 모름.
  • 예: 파일 업로드 (실패할시 성공 보장, 특정 조건에 실행), 백그라운드 비동기 작업
  • 15분마다 실행할 수 있다는 한계가 있음

예제 코드

모델

단순한 시간과 메세지를 저장하기 위해 만든 데이터 클래스

package com.yesnoheun3.alarmmanagerguide
import java.time.LocalDateTime

data class AlarmItem (
    val time: LocalDateTime,
    val message: String
)

인터페이스

추후의 ViewModel에서 사용할 수 있도록 만든 인터페이스.
실제로 ViewModel에서는 context가 필요하지만 인터페이스는 아니다 - 로컬에서 유닛 테스트를 수행할 수는 없다.

package com.yesnoheun3.alarmmanagerguide

interface AlarmScheduler {
    fun schedule(item: AlarmItem)
    fun cancel(item: AlarmItem)
}

리시버

intent로 보낸 메세지를 받는다.
여기서 리포지토리를 사용하거나, 알림을 받거나 비동기 시스템등을 사용할 수 있다.
AndroirManifest에 등록해야한다!

package com.yesnoheun3.alarmmanagerguide

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent

class AlarmReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
    	// 키값으로 메세지를 받음
        val message = intent?.getStringExtra("EXTRA_MESSAGE") ?: return
        println("Alarm triggered: $message")
    }

}

스케쥴러

AlarmManager API를 직접 사용

setExactAndAllowWhileIdle의 파라미터

package com.yesnoheun3.alarmmanagerguide

import android.annotation.SuppressLint
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import java.time.ZoneId

// Context를 참조함
// AlarmScheduler를 상속
class AndroidAlarmScheduler (private val context: Context): AlarmScheduler {
	// AlarmManager 참조 - getSystemService는 OS의 기능을 사용
    private val alarmManager = context.getSystemService(AlarmManager::class.java)

    @SuppressLint("ScheduleExactAlarm")
    override fun schedule(item: AlarmItem) {
    	// 두번째 파라미터: intent를 보내는 클래스
        val intent = Intent(context, AlarmReceiver::class.java).apply {
            putExtra("EXTRA_MESSAGE", item.message)
        }
        
		// 당양한 set 함수가 있다. set, setExact, setInexactRepeating, ...
        // setExactAndAllowWhileIdle: 정확한 시간 + idle(저전력)모드에서도 실행됨
        alarmManager.setExactAndAllowWhileIdle(
        	// 해당 모드: 유저의 디스플레이를 실행한다.
            AlarmManager.RTC_WAKEUP,
            // toEpochSecond를 사용하려면 minSdk가 26이어야한다.
            item.time.atZone(ZoneId.systemDefault()).toEpochSecond() * 1000,
            PendingIntent.getBroadcast(
                context,
                item.hashCode(), // 같은 리퀘스트 코드로 실행될 시 > 업데이트가 된다.
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE // Permission이 필요!
            )
        )
    }

    override fun cancel(item: AlarmItem) {
        alarmManager.cancel(
            PendingIntent.getBroadcast(
                context,
                item.hashCode(), 
                Intent(context, AlarmReceiver::class.java),
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )
        )
    }
}

메인

package com.yesnoheun3.alarmmanagerguide

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.yesnoheun3.alarmmanagerguide.ui.theme.AlarmManagerGuideTheme
import java.time.LocalDateTime

class MainActivity : ComponentActivity() {
    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val scheduler = AndroidAlarmScheduler(this)
        var alarmItem: AlarmItem? = null
        setContent {
            AlarmManagerGuideTheme {
                // A surface container using the 'background' color from the theme
                var secondsText by remember {
                    mutableStateOf("")
                }
                var message by remember {
                    mutableStateOf("")
                }
                Column (modifier = Modifier
                    .fillMaxSize()
                    .padding(16.dp), verticalArrangement = Arrangement.Center) {
                    OutlinedTextField(value = secondsText, onValueChange = {secondsText = it}, modifier = Modifier.fillMaxWidth(), placeholder = {
                        Text(text = "Trigger alarm in seconds")
                    })
                    OutlinedTextField(value = message, onValueChange = {message = it}, modifier = Modifier.fillMaxWidth(), placeholder = {
                        Text(text = "Message")
                    })
                    Row (modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
                        Button(onClick = {
                            alarmItem = AlarmItem(
                                time = LocalDateTime.now().plusSeconds(secondsText.toLong()),
                                message = message
                            )
                            alarmItem?.let(scheduler::schedule)
                            secondsText = ""
                            message = ""
                        }) {
                            Text(text = "Schedule")
                        }
                        Button(onClick = {
                            alarmItem?.let(scheduler::cancel)
                        }) {
                            Text(text = "Cancel")
                        }
                    }
                }
            }
        }
    }
}
profile
하루에 한번 정권 찌르기

0개의 댓글