본문 바로가기
프로그래밍/Android

[Android] 원하는시간에 특정작업 하기 (Alarm Manager)

by Youngs_ 2023. 11. 27.

1. 요약

 

결과

 

이번 글에서는 AlarmManager를 사용하여 Alarm을 정의하고 등록하는 방법에 관하여 기술한다.

 

 

AlarmManager를 통하여 Alarm을 등록하면 특정 시간에 Alarm을 발생하도록 할 수도 있으며, 지정한 시간 간격만큼 반복하여 Alarm을 반복하여 발생시키도록 할 수도 있다.

 

 

이 Alarm을 가장 많이 활용하는 예로 들자면 알림(Notification)이다.

 

Notification

 

단, Alarm 자체는 지정한 시간을 기준으로 Alarm을 발생시키기만 할 뿐 발생하였을 때 어떤 기능을 동작할지는 별도로 구현해야 한다.

 

따라서 AlarmManager를 사용하여 지정한 시간에 Alarm이 발생할 때 알림(Notification)이 동작하도록 정의해주면 흔히 우리가 알고 있는 알림 서비스를 구현해낼 수 있다.

 

이번 글에서는 Notification의 관한 내용 없이 AlarmManager의 관하여 자세히 알아본다.

 

 

 

 

2. 레이아웃

2-1. activity_main.xml

메인 레이아웃으로서 ToggleButton 1개로 구성하였다. 

 

ToggleButton은 Click 할 경우 On/Off가 변경되는 Button으로서 On/Off이 될 경우 각각 Alarm을 On/Off 하도록 구현하였다.

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ToggleButton
        android:id="@+id/toggleButton01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Alarm Start"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.501"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.412" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

 

 

3. 코드 및 설명

3-1. MyAlarmReceiver.kt

Alarm Event를 받기 위한 Broadcast Receiver를 정의한 파일이다.

 

간단하게 설명하자면 Alarm이 발생하였을 때 어떠한 동작을 할지 정의해주는 곳이다.

 

해당 예시에서는 인자로 받은 intent에서 code 값을 비교하여 일치할 경우 Alarm이 정상적으로 동작하는지를 확인하기 위해서 Toast Message와 Log를 사용하여서 확인하고 있다.

 

 

Tip으로는 여기서 전달받은 count값 또한 출력하여 확인하도록 하고 있으며, count 값은 Alarm을 테스트해 볼 경우 코드를 변경할 때마다 intent에서 count값을 +1 해주는 등 변경하여 테스트를 수행하였다.

 

이러한 이유는 이렇게 해야지 그렇지 않으면 이전 남아있던 Alarm이 동작하여 의도치 않은 결과를 얻을 수 있기 때문이다.

 

package com.notepad96.alarmservice

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import android.widget.Toast

class MyAlarmReceiver: BroadcastReceiver() {
    override fun onReceive(p0: Context?, p1: Intent?) {
        if(p1?.extras?.get("code") == MainActivity.REQUEST_CODE) {
            Toast.makeText(p0, "Alarm Start", Toast.LENGTH_SHORT).show()
            var count = p1.getIntExtra("count", 0)
            Log.d("myLog", "$count")
        }
    }
}

 


 

파일을 정의하였다면 이제 Manifest 파일의 receiver로 등록해준다.

 

manifests.xml


 

3-2. MainActivity.kt

메인 파일로서 Alarm을 등록하기 위한 AlarmManager와 PendingIntent를 정의하며 SharedPreferences를 사용하여 Alarm의 On/Off 여부를 저장하여 나타내도록 하였다.

 

 

pendingIntent는 위에서 정의하였던 receiver로 보낼 intent이며 앞서 정의한 것처럼 code와 count 값을 담아서 보내도록 한다.

 

 

Toggle Button의 setOnCheckedChange를 통하여 On/Off가 변경될 때 Event가 동작하도록 정의하였으며 True일 경우 Alarm이 Start 되며 False일 경우 Cancel 되도록 구현하였다.

 

package com.notepad96.alarmservice

import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.os.SystemClock
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import com.notepad96.alarmservice.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    lateinit var setting: SharedPreferences

    companion object {
        const val REQUEST_CODE = 101
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        setting = getSharedPreferences("setting", MODE_PRIVATE)
        binding.toggleButton01.isChecked = setting.getBoolean("alarm", false)

        val alarmManager = binding.root.context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val pendingIntent = Intent(binding.root.context, MyAlarmReceiver::class.java).let {
            it.putExtra("code", REQUEST_CODE)
            it.putExtra("count", 32)
            PendingIntent.getBroadcast(binding.root.context, REQUEST_CODE, it, 0)
        }

        binding.toggleButton01.setOnCheckedChangeListener { _, b ->
            setting.edit {
                putBoolean("alarm", b)
            }

            if(b) {
                // Case 0: 1회성 알람, 10초 후
                alarmManager.set(
                    AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    SystemClock.elapsedRealtime() + 1000 * 10,
                    pendingIntent
                )

                // Case 1: 10초 후 10초 간격으로 Alarm (Min Interval: 1 minute)
//            alarmManager.setInexactRepeating(
//                AlarmManager.ELAPSED_REALTIME_WAKEUP,
//                SystemClock.elapsedRealtime() + 1000 * 10,
//                1000 * 10L,
//                pendingIntent
//            )

                // Case 2: 10초 후 2분 간격으로 Alarm (Interval: 2 minute)
//            alarmManager.setInexactRepeating(
//                AlarmManager.ELAPSED_REALTIME_WAKEUP,
//                SystemClock.elapsedRealtime() + 1000 * 10,
//                1000 * 60L * 2,
//                pendingIntent
//            )

                // Case 3: 오전 8시 27분 Alarm 생성 (Interval: Day)
//                val calendar = Calendar.getInstance().apply {
//                    timeInMillis = System.currentTimeMillis()
//                    set(Calendar.HOUR_OF_DAY, 8)
//                    set(Calendar.MINUTE, 27)
//                }
//                alarmManager.setInexactRepeating(
//                    AlarmManager.RTC_WAKEUP,
//                    calendar.timeInMillis,
//                    AlarmManager.INTERVAL_DAY,
//                    pendingIntent
//                )
                Toast.makeText(applicationContext, "Start", Toast.LENGTH_SHORT).show()
                Log.d("myLog", "Start")
            } else {
                alarmManager.cancel(pendingIntent)
                Toast.makeText(applicationContext, "Cancel", Toast.LENGTH_SHORT).show()
                Log.d("myLog", "Cancel")
            }
        }
    }
}

 

True일 경우 Alarm을 발생하도록 하였으며 총 4개의 Case로 정의하여 구성하였다.

 

 

● Case 0: 1회성 알람

 

1회용 알람을 만들어내며 첫 번째 인자로서 Type이 들어가며 Type으로는 다음과 같이 있다.

ELAPSED_REALTIME 기기가 부팅된 후 경과한 시간을 기반으로 대기 중인 인텐트를 실행하지만 기기의 절전 모드는 해제하지 않습니다. 경과 시간에는 기기가 대기 상태였던 시간이 포함됩니다.
ELAPSED_REALTIME_WAKEUP 기기를 부팅한 후 지정된 시간이 경과하면 기기의 절전 모드를 해제하고 대기 중인 인텐트를 실행합니다.
RTC 지정된 시간에 대기 중인 인텐트를 실행하지만 기기의 절전 모드는 해제하지 않습니다.
RTC_WAKEUP 지정된 시간에 기기의 절전 모드를 해제하여 대기 중인 인텐트를 실행합니다.

 

두 번째 인자로서는 알람을 발생시킬 시간이 들어가며 해당 코드에서는

 

"SystemClock.elapsedRealtime [현재 시간] + 1000 [1초] * 10" = 현재로부터 10초 뒤

 

이처럼 10초 뒤 알람이 발생하도록 하였다.

 

세 번째 인자로서는 앞서 정의하였던 receiver의 전달할 Intent를 넣어준다.

 

 

이렇게 각 값을 넣어주면 최상단의 보이는 결과 화면처럼 Toggle Button을 Click 하면 Start가 되며 10초 후 Alarm이 발생하여 Toast Message가 보이는 것을 확인할 수 있다.

 

 


 

● Case 1: 10초 후 10초 간격으로 Alarm

 

Case 0과는 다르게 setInexactRepeating은 Alarm을 지정한 시간 간격만큼 주기적으로 Alarm을 발생하도록 만든다.

 

때문에 인수를 보면 1개가 더 추가된 4개이며 추가된 세 번째 인수는 Alarm을 발생시킬 주기(Interval)를 지정하며 여기서 반복 주기를 하루로 하면 하루의 한 번씩 발생하는 Alarm을 만들어 낼 수 있는 것이다.

 

 

그러면 정상적으로 Alarm이 등록되며 동작하는지를 확인하기 위해서 Case 1에서는 10초 간격으로 Alarm을 발생시켜 보았다.

Case 1

결과를 확인해 보면 우선 10초 뒤 Alarm이 발생하도록 두 번째 인자 값으로 주었으며 Start가 된 후 10초 후 정상적으로 Alarm이 발생하는 것을 확인할 수 있다.

 

이제 세 번째 인수로 준 반복 주기(10초)로 Alarm이 발생해야 하지만 결과를 확인해보면 1분 간격으로 Alarm이 발생하고 있는 것을 확인할 수 있다.

 

이러한 이유는 Android 5.1(API 22)부터 배터리 소모량 등을 문제로 알람 반복은 최소 1분의 Interval을 갖도록 되었기 때문이다.

 

 

+ 결과를 보면 중간에 "3분 22초 -> 4분 58초 -> 5분 22초"이며 이렇게 오차가 발생할 수도 있다는 걸 볼 수 있다.

 

 


 

● Case 2: 10초 후 2분 간격으로 Alarm

 

동작 메커니즘은 Case 1과 동일하며 Alarm Interval을 2분으로 설정하였다.

 

Case 2

정상적으로 Alarm이 등록되어 발생하는 것을 볼 수 있으며, 결과를 확인해보면 오차가 있는 것을 확인할 수 있다.

 

중간에 "23분 03초 -> 24분 27초"처럼 변수가 있는 것 빼고는 2분 40초 간격으로 Alarm이 발생하고 있는데 이는 그저 발생할 수 있는 오차인지, 잘못된 구현으로 인한 오류인 것인지는 추가적으로 확인이 필요할 것 같다.

 

 


 

● Case 3: 매일 오전 8시 27분 Alarm 발생

 

알람을 등록하는 방법은 동일하며 단지, 추가적으로 Calendar를 생성하여 알람을 발생시킬 시간을 지정해주고 있다.

 

Calendar부분을 살펴보면 우선 System.currentTimeMillis를 현재 시간(날짜)으로 초기화하며 set으로 Alarm을 발생시키고자 하는 시간(HOUR_OF_DAY)과 분(MINUTE)을 지정하였다.

 

 

이제 이 초기화한 calendar를 두 번째 인자로 주어 원하는 시간에 Alarm이 발생하도록 만드며 주기(Interval)로 미리 정의되어 있는 INTERVAL_DAY(1일)을 사용하여 매일 지정된 시간에 반복하여 Alarm이 발생하도록 할 수 있다.

 

 


출처 : https://notepad96.tistory.com/204

 

[Android/Kotlin] AlarmManager - 알람 등록

1. 요약 이번 글에서는 AlarmManager를 사용하여 Alarm을 정의하고 등록하는 방법에 관하여 기술한다. AlarmManager를 통하여 Alarm을 등록하면 특정 시간에 Alarm을 발생하도록 할 수도 있으며, 지정한 시간

notepad96.tistory.com

 

댓글