특정 시간에 Notification 발생 시키기

 

특정 시간이 되면 알림이 발생되는 코드를 android에서 구현해보도록 하겠습니다. 특정 시간을 설정해서 그 시간이 되면 알려주는 무엇인가가 있어야하는데 이를 가능하게 만들어주는 것이 AlarmManager입니다. 코드를 통해서 알아보도록 합시다.

 

먼저 레이아웃은 간단하게 아래처럼 구성되어있습니다.

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">

    <TimePicker
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:timePickerMode="spinner"
        android:id="@+id/time_picker" />


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="저장"
        android:id="@+id/save"/>

</LinearLayout>

 

이제 time_picker에서 시간을 설정하고 save 버튼을 누르게 되면 그 시간에 Notification이 발생하게 되는 코드를 설명합니다.

 

MainActivity

MainActivity의 onCreate 코드는 아래와 같습니다. 

package com.reak.alarmtest;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.TimePicker;
import android.widget.Toast;

import java.util.Calendar;

public class MainActivity extends AppCompatActivity {

    private Button save;
    private TimePicker timePicker;

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        timePicker=(TimePicker)findViewById(R.id.time_picker);
        save=(Button)findViewById(R.id.save);

        save.setOnClickListener(v->{

            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(System.currentTimeMillis());
            int hour=timePicker.getHour();
            int minute=timePicker.getMinute();
            calendar.set(Calendar.HOUR_OF_DAY,hour);
            calendar.set(Calendar.MINUTE,minute);

            if (calendar.before(Calendar.getInstance())) {
                calendar.add(Calendar.DATE, 1);
            }

            AlarmManager alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
            if (alarmManager != null) {
                Intent intent = new Intent(this, AlarmReceiver.class);
                PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);

                alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                        AlarmManager.INTERVAL_DAY, alarmIntent);

                Toast.makeText(MainActivity.this,"알람이 저장되었습니다.",Toast.LENGTH_LONG).show();
            }
        });
    }
}

 

여기서 Calendar 객체를 현재시간으로 미리 설정해두고, set 메소드로 timePicker에서 설정된 시간과 분으로 설정시키는 것입니다. 이때 시간과 분을 TimePicker에서 얻어오려면 @RequiresApi(api = Build.VERSION_CODES.M) 를 메소드위에 추가시켜야합니다.

이제 AlarmManager를 가져옵니다. Intent는 수신자 클래스를 전달하게 됩니다. 여기서는 AlarmReceiver라는 수신자이며 밑에서 이 Receiver를 구현하게 될겁니다. 그리고 PendingIntent를 얻어와서 setRepeating 메소드로 정확한 시간에 알람을 설정시켜줍니다.

 

여기서 setRepeating메소드는 알람을 반복시키는 메소드입니다. 여기서는 AlarmManager.INTERVAL_DAY를 사용하였고, 이것은 매일 알람이 울릴것을 명시해준것입니다. 

 

굳이 정확한 시간에 알람을 울리지 않을 경우에는 setInexactRepeating 메소드를 사용할 수도 있습니다. 실제로 안드로이드 개발문서에는 이 메소드를 사용하라고 권고하고 있네요.

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.reak.alarmtest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AlarmTest">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".AlarmReceiver"/>

    </application>

</manifest>

 

수신자를 사용하려면 위에서처럼 메니페스트에 수신자를 명시해주어야합니다. 

 

AlarmReceiver

package com.reak.alarmtest;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.RingtoneManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

import androidx.core.app.NotificationCompat;

import java.util.Calendar;
import java.util.StringTokenizer;

import static android.app.Notification.EXTRA_NOTIFICATION_ID;

public class AlarmReceiver extends BroadcastReceiver {

    private Context context;
    private String channelId="alarm_channel";
    @Override
    public void onReceive(Context context, Intent intent) {
        this.context = context;


        Intent busRouteIntent = new Intent(context, MainActivity.class);

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
        stackBuilder.addNextIntentWithParentStack(busRouteIntent);
        PendingIntent busRoutePendingIntent =
                stackBuilder.getPendingIntent(1, PendingIntent.FLAG_UPDATE_CURRENT);

        final NotificationCompat.Builder notificationBuilder=new NotificationCompat.Builder(context,channelId)
                .setSmallIcon(R.mipmap.ic_launcher).setDefaults(Notification.DEFAULT_ALL)
                .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
                .setAutoCancel(true)
                .setContentTitle("알람")
                .setContentText("울림")
                .setContentIntent(busRoutePendingIntent);


        final NotificationManager notificationManager=(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);

        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
            NotificationChannel channel=new NotificationChannel(channelId,"Channel human readable title",NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        int id=(int)System.currentTimeMillis();

        notificationManager.notify(id,notificationBuilder.build());

    }
}

 

이제 알람이 발생되면 AlarmReceiver의 onReceive 메소드가 호출되게 됩니다. 이때 아까 우리가 MainActivity에서 넘긴 PendingIntent를 requestCode를 통해서 얻어올 수가 있습니다. 이 requestCode는 알람을 설정했던 것과 같은 코드를 사용해야합니다. 여기서는 1입니다.

 

오레오(Oreo)버전 이상부터는 NotificationChannel을 명시해주어야합니다. 그 코드도 적용되어 있습니다.

 

대충 완성하게 되면 아래와 같은 모습입니다. 앱 디자인은 개나 줘버린 모습이지만 구현이 제대로 되었나 확인만 하는 용도입니다.

alarm test 앱화면

 

그리고 원하는 시간에 알람을 저장시켜줍니다. 그래서 9시 15분에 알람을 설정합니다. 그리고 그 시간에 울리는 지 잠깐 기다려주겠습니다. 이때 9시 14분이었고, 1분만 기다리면 됩니다.

alarm 저장

 

다음은 알람이 발생되어 Notification이 발생된 화면입니다.

 

분명 9시 15분이었는데, 9시 16분에 알람이 발생되었군요. 정확한 시간에 알람이 딱 울리지는 않는 것 같습니다. 제 생각에는 setRepeating이 setInexactRepeating메소드보다 더 정확한 시간에 발생시켜주는 것으로 확인이 됩니다.

알람 취소

그리고 알람을 취소하려면 아래와 같이 코드를 추가하면 되는데요.  위의 코드는 없지만 만약 알람 취소버튼을 추가하고 그 버튼이 눌리면 alarmManager의 cancel 메소드를 이용하면 됩니다.

PendingIntent cancelIntent=PendingIntent.getBroadcast(MainActivity.this, 1, intent,PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.cancel(cancelIntent);

 

 

여기까지 Alarm과 Notification, Receiver를 조합하여 앱을 구현해보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,