Android/Kotlin

[Android][Kotlin] 알람매니저(AlarmManager)를 이용한 알림(Notification) 호출하기

자판을 두드리다 2022. 10. 14. 15:40
320x100

특정 시간, 날짜 등 시간을 기준으로 하여

알림을 울리는 작업을 하고자 한다

 

사실 AlarmManager 만 있는건 아니지만

도즈모드의 문제 때문에 AlarmManager 를 선택했다

 

도즈모드에 대해서는 아래

android 공식문서에서 확인 하면 될 듯 싶다

https://developer.android.com/training/monitoring-device-state/doze-standby

 

잠자기 및 앱 대기 모드에 맞게 최적화  |  Android 개발자  |  Android Developers

앱에서 Android 6.0의 절전 기능을 테스트하고 최적화합니다.

developer.android.com

 

이번에는 사용자가 특정 날짜를 기입함으로써

기입한 날짜에 알림을 울리게 해봤다

 

BroadcastReceiver로 받고 알림을 울리기 때문에

어플을 끈 상태이거나, 화면이 꺼져 있더라도

 

알림이 울리며,

도즈모드에서도 울리게 되어 있으니

 

대기 시간이 길더라도 울릴 것으로

확인된다

 

 

코드 1 : MainActivity.kt

아래 코드에서 주의 할 점이 있는데

이번 코드의 알람매니저의 경우 RTC 기준으로 시간을 잡았으며,

 

버전마다 사용하는 함수가 틀리므로

지원범위를 생각해서 코드를 작성하면 될 것 같다

 

class MainActivity : AppCompatActivity() {
    private lateinit var edYMD: EditText
    private lateinit var edHMS: EditText
    private lateinit var bAlarmStart: Button
    private lateinit var bAlarmCancel: Button
    private lateinit var tvMsg: TextView

    private lateinit var alarmManager: AlarmManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        edYMD = findViewById(R.id.et_ymd)
        edHMS = findViewById(R.id.et_hms)
        bAlarmStart = findViewById(R.id.b_alarmStart)
        bAlarmCancel = findViewById(R.id.b_alarmCancel)
        tvMsg = findViewById(R.id.tv_msg)

        // 알림 매니저 객체 가져오기
        alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager

        bAlarmStart.setOnClickListener { alarmStart() }
        bAlarmCancel.setOnClickListener { alarmCancel() }
    }

    // 년월일, 시분초 확인 후 알림 등록
    private fun alarmStart() {
        if(edYMD.text.length != 8) { // 년월일 확인
            tvMsg.text = "년월일 8자리의 범위를 확인해 주세요"
        } else if(edHMS.text.length != 6) { // 시분초 확인
            tvMsg.text = "시분초 6자리의 범위를 확인해 주세요"
        } else { // 알림 등록 시작
            val calendar = getCalendar(edYMD.text.toString(), edHMS.text.toString())
            calendar?.let {
                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    alarmManager.setExactAndAllowWhileIdle(
                        AlarmManager.RTC_WAKEUP,
                        calendar.timeInMillis,
                        getPendingIntent()
                    )
                } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    alarmManager.setExact(
                        AlarmManager.RTC_WAKEUP,
                        calendar.timeInMillis,
                        getPendingIntent()
                    )
                } else {
                    alarmManager.set(
                        AlarmManager.RTC_WAKEUP,
                        calendar.timeInMillis,
                        getPendingIntent()
                    )
                }

                tvMsg.text = "알림 등록을 완료하셨습니다"
            }
        }
    }

    // 입력한 년월일, 시분초에 따라 calendar 작성
    private fun getCalendar(ymd: String, hms: String): Calendar? {
        val calendar = Calendar.getInstance()
        val simpleDateFormat = SimpleDateFormat("yyyyMMdd kkmmss", Locale.KOREA)
        val date = simpleDateFormat.parse("$ymd $hms")
        try {
            calendar.time = date
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }
        return calendar
    }

    // 등록한 알림 삭제
    private fun alarmCancel() {
        alarmManager.cancel(getPendingIntent())
        tvMsg.text = "알림을 취소하셨습니다"
    }

    // api 버전에 따라 PendingIntent 가져오기
    private fun getPendingIntent(): PendingIntent {
        val intent = Intent(this, AlarmReceiver::class.java)
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.getBroadcast(
                this, AlarmReceiver.ALARM_ID, intent,
                PendingIntent.FLAG_MUTABLE
            )
        } else {
            PendingIntent.getBroadcast(
                this, AlarmReceiver.ALARM_ID, intent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )
        }
    }
}

 

코드 2 : AlarmReceiver.kt

class AlarmReceiver : BroadcastReceiver() {
    companion object {
        const val ALARM_ID = 0
        const val NOTIFICATION_CHANNEL = "test_real_alarm_notification"
    }

    private lateinit var notificationManager: NotificationManager

    override fun onReceive(context: Context, intent: Intent) {
        // 알림 매니저
        notificationManager = context.getSystemService(
            Context.NOTIFICATION_SERVICE) as NotificationManager

        notificationShow(context)
    }

    private fun notificationShow(context: Context) {
        createNotificationChannel()
        // 알림 생성
        val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("Alarm Notification Title")
            .setContentText("알림이 도착하였습니다")
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setAutoCancel(true)
            .setDefaults(NotificationCompat.DEFAULT_ALL)

        // 알림 호출
        notificationManager.notify(0, builder.build())
    }

    // 채널 생성 - 오레오 버전 이상 부터
    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationChannel = NotificationChannel(
                NOTIFICATION_CHANNEL,
                "Stand up notification",
                NotificationManager.IMPORTANCE_HIGH
            )
            notificationChannel.enableLights(true)
            notificationChannel.lightColor = Color.RED
            notificationChannel.description = "Notification Test"
            notificationManager.createNotificationChannel(
                notificationChannel)
        }
    }
}

 

코드 3 : activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_ymd"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="5dp"
            android:layout_marginTop="5dp"
            android:text="[년월일 8자리 (예 : 20220101)]"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/et_ymd"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="20dp"
            android:layout_marginTop="5dp"
            android:layout_marginEnd="20dp"
            android:ems="10"
            android:hint="년월일 8자리를 입력해 주세요"
            android:inputType="number"
            android:textAlignment="center"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_ymd" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp">

        <TextView
            android:id="@+id/tv_hms"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="5dp"
            android:layout_marginTop="5dp"
            android:text="[시분초 6자리 (예 : 153020)]"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/et_hms"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="20dp"
            android:layout_marginTop="5dp"
            android:layout_marginEnd="20dp"
            android:ems="10"
            android:hint="시분초 6자리를 입력해 주세요"
            android:inputType="number"
            android:textAlignment="center"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_hms" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingStart="10dp"
        android:paddingTop="10dp"
        android:paddingEnd="10dp"
        android:paddingBottom="10dp">

        <Button
            android:id="@+id/b_alarmStart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="알림 설정" />

        <Space
            android:layout_width="10dp"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/b_alarmCancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="알림 취소" />
    </LinearLayout>

    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="none"
        android:textAlignment="center"
        android:textAppearance="@style/TextAppearance.AppCompat.Large" />
</LinearLayout>
320x100