Menampilkan Notifikasi Setiap Jam dengan Service

Bagikan jika Anda sukai postingan ini!

Tampilan notifikasi
Tampilan notifikasi

Melanjutkan pembahasan tentang perizinan pada artikel sebelumnya, seperti sudah dijelaskan mengenai mulai otomatis (auto start), berikut ini saya akan membuat aplikasi yang memanfaatkan mulai otomatis, di mana aplikasi akan menjalankan sebuah service saat perangkat dinyalakan ulang (reboot). Service dari aplikasi ini akan menampilkan pesan notifikasi setiap jam.

Kita mulai dengan membuat sebuah proyek baru dengan Basic Activity sebagai pilihan pola main activity. Atur proyek Anda seperti di bawah ini.

  • Name: Hourly Notification
  • Package: com.inochi.notification
  • Language: Java
Pengaturan proyek baru
Pengaturan proyek baru Hourly Notification

Saya akan menambahkan beberapa kelas baru pada proyek ini. Agar lebih memudahkan dalam pengelompokkan kelas, saya menambahkan beberapa paket baru pada proyek di antaranya: helper, item, service, dan util.

Untuk menambahkan paket baru, lakukan langkah berikut ini. Pada panel Project, perluas cabang app, kemudian java, kemudian nama paket (com.inochi.notification) dengan mengklik ganda pada nama cabang tersebut.

Cabang nama paket pada panel Project
Cabang nama paket pada panel Project

Selanjutnya klik kanan pada cabang nama paket (com.inochi.notification), sorot menu New, kemudian klik Package. Pada dialog New Package beri nama paket baru yaitu helper. Lakukan cara ini kembali untuk membuat paket-paket baru lainnya, yaitu: item, service, dan util.

Paket-paket baru pada proyek
Paket-paket baru pada proyek

Kita akan menambahkan beberapa kelas pada paket helper. Klik kanan pada cabang helper, sorot New, kemudian klik Java Class. Kelas pertama yang akan kita buat bernama Constants.

Dialog pembuatan kelas baru Constants
Dialog pembuatan kelas baru Constants

Lengkapi kode sumber untuk kelas Constants menjadi seperti di bawah ini.

package com.inochi.notification.helper;

public final class Constants {
    public static class Default {
        public static final String APP = "com.inochi.notification";
        private static final int REQUEST = 2000;
    }

    public static final class Action {
        public static final String CLOSE_NOTIFY = Default.APP + ".action." + "CLOSE_NOTIFY";
        public static final String SHOW_NOTIFY = Default.APP + ".action." + "SHOW_NOTIFY";
        public static final String CREATE_DAILY = Default.APP + ".action." + "CREATE_DAILY";
    }

    public static final class Setting {
        public static final String BOOT_PERMISSION = Default.APP + ".setting." + "BOOT_PERMISSION";
        public static final String NOTIF_ID = Default.APP + ".setting." + "NOTIF_ID";
        public static final String NOTIF_TITLE = Default.APP + ".setting." + "NOTIF_TITLE";
        public static final String NOTIF_TEXT = Default.APP + ".setting." + "NOTIF_TEXT";
        public static final String NOTIF_ICON = Default.APP + ".setting." + "NOTIF_ICON";
    }

    public static final class Permission {
        public static final class Type {
            public static final int RECEIVE_BOOT_COMPLETED = Default.REQUEST + 1;
            public static final int WAKE_LOCK = Default.REQUEST + 2;
            public static final int VIBRATE = Default.REQUEST + 3;
        }
    }
}

Masih pada paket helper, tambahkan kelas baru dengan nama Settings kemudian lengkapi kode sumbernya menjadi seperti di bawah ini.

package com.inochi.notification.helper;

import android.content.Context;
import android.content.SharedPreferences;

public class Settings {
    private SharedPreferences settings;
    private Context context;
    private String name;

    public Settings(Context context, String name){
        this.context = context;
        this.name = name;
        settings = context.getSharedPreferences(name, Context.MODE_PRIVATE);
    }

    public Settings(Context context){
        this(context, context.getPackageName());
    }

    public void setSetting(String key, String value){
        SharedPreferences.Editor editor = settings.edit();
        editor.putString(key, value);
        editor.apply();
    }

    public void deleteSetting(){
        settings = context.getSharedPreferences(name, Context.MODE_PRIVATE);
        settings.edit().clear().apply();
    }

    public String getSetting(String key, String defVal){
        return settings.getString(key, defVal);
    }

    public String getSetting(String key){
        return settings.getString(key, "");
    }

    public int getIntSetting(String strKey, int defVal){
        String strValue = String.valueOf(defVal);
        String strSetting = getSetting(strKey, strValue);

        return Integer.parseInt(strSetting);
    }

    public int getIntSetting(String strKey){
        String strSetting = getSetting(strKey);
        return Integer.parseInt(strSetting);
    }

    public void setIntSetting(String strKey, int defVal){
        String strValue = String.valueOf(defVal);
        setSetting(strKey, strValue);
    }

    public short getShortSetting(String strKey, short defVal){
        String strValue = String.valueOf(defVal);
        String strSetting = getSetting(strKey, strValue);
        return Short.parseShort(strSetting);
    }

    public void setShortSetting(String strKey, short defVal){
        String strValue = String.valueOf(defVal);
        setSetting(strKey, strValue);
    }

    public long getLongSetting(String strKey, long defVal){
        String strValue = String.valueOf(defVal);
        String strSetting = getSetting(strKey, strValue);

        return Long.parseLong(strSetting);
    }

    public long getLongSetting(String strKey){
        String strSetting = getSetting(strKey);
        return Long.parseLong(strSetting);
    }

    public void setLongSetting(String strKey, long defVal){
        String strValue = String.valueOf(defVal);
        setSetting(strKey, strValue);
    }
}

Dan masih pada paket helper, tambahkan sebuah kelas baru lagi dengan nama BundleSettings, dan lengkapi kode sumbernya seperti di bawah ini.

package com.inochi.notification.helper;

import android.content.Context;

public class BundleSettings {
    private Context context;
    private Settings settings;

    public BundleSettings(Context context){
        this.context = context;
        this.settings = new Settings(context);
    }

    public int getBootPremission(){
        return settings.getIntSetting(Constants.Setting.BOOT_PERMISSION, 0);
    }

    public void setBootPremission(int value){
        settings.setIntSetting(Constants.Setting.BOOT_PERMISSION, value);
    }
}

Kegunaan kelas Constants, Settings, dan BundleSettings sama persis seperti dijelaskan pada artikel sebelumnya.

Kelas-kelas baru pada paket helper
Kelas-kelas baru pada paket helper

Berikutnya kita akan menambahkan sebuah kelas baru pada paket item, dengan nama NotifItem. Kelas ini digunakan sebagai obyek untuk menampung informasi atau properti dari notifikasi yang nantinya akan ditampilkan. Lengkapi kode sumbernya menjadi seperti di bawah ini.

package com.inochi.notification.item;

import java.io.Serializable;

public class NotifItem implements Serializable {
    private int id;
    private String ticker;
    private String title;
    private String message;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getTicker() {
        return ticker;
    }

    public void setTicker(String ticker) {
        this.ticker = ticker;
    }
}

Selanjutnya kita akan menambahkan sebuah komponen notifikasi (UI Notification) pada paket util. Klik kanan cabang util, sorot menu New, sorot menu UI Component, kemudian klik menu Notification.

Pada dialog wisaya yang ditampilkan, isi Class Name dengan TestNotification. Ubah Style when Expanded menjadi None, klik tombol Next untuk melanjutkan ke wisaya berikutnya.

Dialog menambahkan komponen Notification
Dialog menambahkan komponen Notification

Tidak perlu melakukan apapun pada dialog wisaya berikutnya (Generate Icon), akhiri saja dengan mengklik tombol Finish.

Ubah kode sumber kelas TestNotification menjadi seperti di bawah ini.

package com.inochi.notification.util;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.support.v4.app.NotificationCompat;

import com.inochi.notification.R;
import com.inochi.notification.helper.Constants;
import com.inochi.notification.item.NotifItem;
import com.inochi.notification.service.TestReceiver;

public class TestNotification {
    public static void notify(final Context context, NotifItem notifItem) {
        final Resources res = context.getResources();

        final Bitmap picture = BitmapFactory.decodeResource(res, R.drawable.example_picture);

        final int id = notifItem.getId();
        final String ticker = notifItem.getTicker();
        final String title = notifItem.getTitle();
        final String text = notifItem.getMessage();

        Intent intentReceiver = new Intent(context, TestReceiver.class);
        intentReceiver.putExtra(Constants.Setting.NOTIF_ID, id);
        intentReceiver.setAction(Constants.Action.CLOSE_NOTIFY);

        PendingIntent closeNotifyIntent = PendingIntent.getBroadcast(context,
                id, intentReceiver, PendingIntent.FLAG_UPDATE_CURRENT);

        long[] pattern = null;
        NotificationManager notificationManager = createNotificationChannel(context);

        final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, Constants.Default.APP)
                .setDefaults(NotificationCompat.FLAG_AUTO_CANCEL)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setSmallIcon(R.drawable.ic_stat_test)
                .setContentTitle(title)
                .setContentText(text)
                .setLargeIcon(picture)
                .setTicker(ticker)
                .setVibrate(pattern)
                .setStyle(new NotificationCompat.BigTextStyle().bigText(text))
                .setAutoCancel(true)
                .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Close", closeNotifyIntent)
                ;

        notificationManager.notify(id, builder.build());
    }

    private static NotificationManager createNotificationChannel(Context context) {
        NotificationManager notificationManager;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            notificationManager = context.getSystemService(NotificationManager.class);
        } else {
            notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = context.getString(R.string.app_name);
            String description = context.getString(R.string.app_name);
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(Constants.Default.APP, name, importance);
            channel.setDescription(description);
            notificationManager.createNotificationChannel(channel);
        }

        return notificationManager;
    }

    public static void cancel(final Context context, int id) {
        final NotificationManager nm = createNotificationChannel(context);
        nm.cancel(id);
    }
}

Abaikan dulu jika terdapat kesalahan pada kode sumber kelas TestNotification, karena berhubungan dengan proses berikutnya. Kelas TestNotification digunakan sebagai UI dari notifikasi yang akan ditampilkan oleh aplikasi.

Berikutnya kita akan menambahkan dua buah kelas pada paket service. Klik kanan pada cabang service, sorot menu New, sorot menu Other, kemudian klik Broadcast Receiver. Cukup isikan Class Name dengan TestReceiver kemudian klik tombol Finish.

Dialog menambahkan kelas Receiver
Dialog menambahkan kelas Receiver

Ubah kode sumber kelas TestReceiver menjadi seperti di bawah ini.

package com.inochi.notification.service;

import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;

public class TestReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action == null) action = "";

        if (!action.isEmpty()){
            Intent service = new Intent(context, MainService.class);
            service.setAction(action);
            service.putExtras(intent);
            startWakefulService(context, service);
        }
    }
}

Abaikan juga jika terdapat kesalahan pada kode sumber TestReceiver. Kelas ini digunakan untuk menangkap kiriman perintah brodcast dari komponen aplikasi yang kemudian akan diteruskan ke sebuah service.

Selanjutnya kita akan menambahkan kelas kedua pada paket service. Klik kanan pada cabang service, sorot menu New, sorot menu Service, kemudian klik menu Service. Isi Class Name dengan MainService, kemudian akhiri wisaya dengan mengklik tombol Finish.

Dialog menambahkan kelas Service
Dialog menambahkan kelas Service

Ubah kode sumber kelas MainService menjadi seperti di bawah ini.

package com.inochi.notification.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;

import com.inochi.notification.helper.Constants;
import com.inochi.notification.item.NotifItem;
import com.inochi.notification.util.TestNotification;

import java.util.Calendar;

public class MainService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    public MainService() {
        super();
        mName = "MainService";
    }

    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void onHandleIntent(Intent intent){
        String action = intent.getAction();

        if (action == null) action = "";

        if (!action.isEmpty()){
            Bundle args = intent.getExtras();
            assert args != null;
            NotifItem notifItem = null;

            int notifId = args.getInt(Constants.Setting.NOTIF_ID);
            String notifTitle = args.getString(Constants.Setting.NOTIF_TITLE);
            String notifText = args.getString(Constants.Setting.NOTIF_TEXT);

            if (notifId > 0){
                notifItem = new NotifItem();
                notifItem.setId(notifId);
                notifItem.setTicker(notifTitle);
                notifItem.setTitle(notifTitle);
                notifItem.setMessage(notifText);
            }

            switch (action){
                case "com.htc.intent.action.QUICKBOOT_POWERON":
                case "android.intent.action.QUICKBOOT_POWERON":
                case "android.intent.action.BOOT_COMPLETED":
                case Constants.Action.CREATE_DAILY:
                    createDailyAlarm();
                    break;
                case Constants.Action.SHOW_NOTIFY:
                    if (notifId > 0)
                        TestNotification.notify(this, notifItem);
                    break;
                case Constants.Action.CLOSE_NOTIFY:
                    if (notifId > 0)
                        TestNotification.cancel(this, notifId);
                    break;
            }

            TestReceiver.completeWakefulIntent(intent);
        }
    }

    private void createDailyAlarm(){
        Calendar calendar = Calendar.getInstance();
        int hour = calendar.get(Calendar.HOUR_OF_DAY);

        AlarmManager alarmMgr;
        PendingIntent alarmIntent;

        alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(this, TestReceiver.class);
        intent.setAction(Constants.Action.SHOW_NOTIFY);

        Bundle args = new Bundle();
        args.putInt(Constants.Setting.NOTIF_ID, 10002);
        args.putString(Constants.Setting.NOTIF_TITLE, "Test Notification");
        args.putString(Constants.Setting.NOTIF_TEXT, "Test Notification for " + hour);

        intent.putExtras(args);

        alarmIntent = PendingIntent.getBroadcast(this, 0, intent, 0);

        alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                60 * 60 * 1000, alarmIntent);

        ComponentName receiver = new ComponentName(this, TestReceiver.class);
        PackageManager pm = getPackageManager();

        pm.setComponentEnabledSetting(receiver,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);
    }
}

Selanjutnya buka berkas manifest dengan mengklik ganda cabang manifests di panel Project. Lengkapi skripnya sehingga menjadi seperti di bawah ini.

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

    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <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/AppTheme"
        tools:ignore="AllowBackup,GoogleAppIndexingWarning">

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <receiver
            android:name=".service.TestReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
                <action android:name="com.inochi.notification.action.CREATE_DAILY" />
                <action android:name="com.inochi.notification.action.CLOSE_NOTIFY" />
            </intent-filter>
        </receiver>

        <service
            android:name=".service.MainService"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
                <action android:name="com.inochi.notification.action.CREATE_DAILY" />
                <action android:name="com.inochi.notification.action.CLOSE_NOTIFY" />
            </intent-filter>
        </service>
    </application>

</manifest>

Terakhir tinggal mengubah kode sumber untuk kelas MainActivity menjadi seperti di bawah ini.

package com.inochi.notification;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;

import com.inochi.notification.helper.BundleSettings;
import com.inochi.notification.helper.Constants;
import com.inochi.notification.item.NotifItem;
import com.inochi.notification.service.TestReceiver;
import com.inochi.notification.util.TestNotification;

import java.util.Calendar;

public class MainActivity extends AppCompatActivity {
    private BundleSettings bundleSettings;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        bundleSettings = new BundleSettings(this);
        setPermission();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        try {
            setPermission();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @SuppressLint("InlinedApi")
    private void setPermission(){
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_BOOT_COMPLETED)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECEIVE_BOOT_COMPLETED},
                    Constants.Permission.Type.RECEIVE_BOOT_COMPLETED);
        } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.WAKE_LOCK)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WAKE_LOCK},
                    Constants.Permission.Type.WAKE_LOCK);
        } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.VIBRATE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.VIBRATE},
                    Constants.Permission.Type.VIBRATE);
        } else {
            if (bundleSettings.getBootPremission() == 0){
                runAutoStartCustom();
            }
            runService();
            runFirstNotification();
        }
    }

    private static final Intent[] AUTO_START_INTENTS = {
            new Intent().setComponent(new ComponentName("com.samsung.android.lool",
                    "com.samsung.android.sm.ui.battery.BatteryActivity")),
            new Intent("miui.intent.action.OP_AUTO_START").addCategory(Intent.CATEGORY_DEFAULT),
            new Intent().setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")),
            new Intent().setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity")),
            new Intent().setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity")),
            new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity")),
            new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.startupapp.StartupAppListActivity")),
            new Intent().setComponent(new ComponentName("com.oppo.safe", "com.oppo.safe.permission.startup.StartupAppListActivity")),
            new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity")),
            new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager")),
            new Intent().setComponent(new ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity")),
            new Intent().setComponent(new ComponentName("com.asus.mobilemanager", "com.asus.mobilemanager.entry.FunctionActivity")).setData(
                    Uri.parse("mobilemanager://function/entry/AutoStart"))
    };

    private void runAutoStartCustom(){
        for (Intent intent : AUTO_START_INTENTS){
            if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
                startActivity(intent);

                bundleSettings.setBootPremission(1);
                break;
            }
        }
    }

    private void runService(){
        Intent intent = new Intent(this, TestReceiver.class);
        intent.setAction(Constants.Action.CREATE_DAILY);
        sendBroadcast(intent);
    }

    private void runFirstNotification(){
        Calendar calendar = Calendar.getInstance();
        int hour = calendar.get(Calendar.HOUR_OF_DAY);

        NotifItem notifItem = new NotifItem();
        notifItem.setId(hour);
        notifItem.setTicker("Test Notification");
        notifItem.setTitle("Test Notification");
        notifItem.setMessage("Test Notification from Main Activity");

        TestNotification.notify(this, notifItem);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Jalankan aplikasi Anda. Pertama kali akan muncul notifikasi yang berasal dari MainActivity, dilanjutkan notifikasi yang menandakan proses service sedang berjalan. Selanjutnya tunggu satu jam berikutnya untuk ditampilkan kembali notifikasi.

Tampilan notifikasi saat aplikasi dijalankan
Tampilan notifikasi saat aplikasi dijalankan

Untuk pengujian, Anda dapat mempercepat proses menampilkan notifikasi ini dengan mengubah sedikit kode sumber dari kelas MainService di paket service. Pada baris ke 149, ubah nilai 60 * 60 * 1000 menjadi 60 * 1000, yang artinya Anda meminta notifikasi untuk ditampilkan setiap semenit sekali.

Anda dapat mengunduh contoh proyek melalui tautan berikut ini: https://github.com/InochiSoft/Android-Hourly-Notification