이번 프로젝트에서는 특정 시간에 정해진 작업을 수행하는 기능을 구현해야 했다.
가장 먼저 생각난 방법은 Workmanager와 AlarmManager였다.
오랜 시간동안 지속적으로 수행되는 작업이 아니다 보니 Service로 지속적으로 돌릴 필요는 없을 것 같았다.
먼저 AlarmManager 를 이용해서 기능을 구현했다.
class AlarmReceiver : BroadcastReceiver() {
private var notificationManager: NotificationManager? = null
@SuppressLint("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context, intent: Intent) {
if (notificationManager == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "deajeon_station"
val descriptionText = "Deajeon Station channel"
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel("deajeon_station", name, importance).apply {
description = descriptionText
enableVibration(true)
}
notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager?.createNotificationChannel(channel)
}
}
Log.d(TAG, "알람이 울림 ${intent.action}")
if (intent.action == "ALARM_RINGING") {
val keyword = intent.getStringExtra("keyword")
val formattedValue = intent.getStringExtra("formattedValue")
if (keyword != null && formattedValue != null) {
showNotification(context, keyword, formattedValue, SplashActivity::class.java)
}
val uiUpdateIntent= Intent("kr.deajeon.attendant.EVENT_UPDATE_RECYCLER_VIEW")
LocalBroadcastManager.getInstance(context).sendBroadcast(uiUpdateIntent)
...
}
그리고 디바이스가 종료되었다가 재 시작 되면 알람이 삭제되기 때문에 부팅이 완료되면 다시 알람을 등록하도록 했다.
...
else if (intent.action.equals("android.intent.action.BOOT_COMPLETED")) {
Log.d(TAG, "onReceive BOOT_COMPLETED")
CoroutineScope(Dispatchers.Default).launch {
getInstance().performDailyTask()
}
}
...
비교적 잘 동작하는 것 처럼 보였으나, 앱이 완전히 종료되면 알람도 모두 사라졌다.😥 앱을 완전히 종료할 경우 메모리가 모두 지워지기 때문이라고 한다.
다음은 workManager를 이용하여 매일 자정에 작업을 수행하도록 작성한 코드이다.
...
val workRequest = PeriodicWorkRequestBuilder<DailyWorker>(1, TimeUnit.DAYS)
.setInitialDelay(timeUntilMidnight, TimeUnit.MILLISECONDS)
.setBackoffCriteria(BackoffPolicy.LINEAR,1,TimeUnit.SECONDS) // 시스템이 중단시켜도 자동으로 재개
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"daily",
ExistingPeriodicWorkPolicy.UPDATE,
workRequest
)
...
하지만 무엇때문인지 정확히 자정에 실행되지 않았다.
workmanager 는 시스템 과부하나 배터리 소모를 줄이기 위해 os에 의해 늦춰질 수 있다고 한다
그래서 시스템 브로드캐스트를 이용해보기로 했다. 시스템에서 전달하는 인텐트는 정확하게 동작 할 것이라고 생각했다.
그래서 브로드캐스트 리시버에 아래 내용을 추가했다.
...
else if (intent.action.equals(Intent.ACTION_DATE_CHANGED)) {
Log.d(TAG, "onReceive ACTION_DATE_CHANGED")
CoroutineScope(Dispatchers.Main).launch {
getInstance().performDailyTask()
}
}
...
확인 결과 자정에 잘 동작하는것을 확인했다.🤗
2023-08-02 00:00:00.424 28543-28543 D 알람이 울림 android.intent.action.DATE_CHANGED
2023-08-04 00:00:00.117 5148-5148 D 알람이 울림 android.intent.action.DATE_CHANGED
adb로 등록된 알람목록을 확인해보면
잘 등록되었다!
정확한 시간에 수행하기 위해서 foreground service를 이용하는 방법도 있지만, 사용자가 알 필요가 없는 작업의 수행상태를 사용자에게 지속적으로 보여줄 필요가 없다고 생각했다. 때문에 이 방법은 고려하지 않았다.
좋은 글이네요. 공유해주셔서 감사합니다.