Menjadwalkan alarm berulang

Alarm (berdasarkan class AlarmManager) memberikan cara untuk melakukan operasi berbasis waktu di luar masa aktif aplikasi Anda. Misalnya, Anda dapat menggunakan alarm untuk memulai operasi yang berjalan lama, seperti memulai layanan sekali dalam satu hari untuk mendownload perkiraan cuaca.

Alarm memiliki karakteristik berikut:

  • Alarm memungkinkan Anda memicu Intent pada waktu/interval yang ditentukan.
  • Anda dapat menggunakannya bersama dengan penerima siaran untuk memulai layanan dan melakukan operasi lain.
  • Alarm beroperasi di luar aplikasi Anda, sehingga dapat digunakan untuk memicu peristiwa atau tindakan bahkan saat aplikasi Anda sedang tidak berjalan, dan meskipun perangkat itu sendiri dalam mode tidur.
  • Alarm membantu meminimalkan kebutuhan resource aplikasi Anda. Anda dapat menjadwalkan operasi tanpa mengandalkan timer atau menjalankan layanan latar belakang secara terus-menerus.

Catatan: Untuk pemilihan waktu operasi yang dijamin terjadi selama masa aktif aplikasi Anda, pertimbangkan untuk menggunakan class Handler bersama dengan Timer dan Thread. Pendekatan ini memberi Android kontrol yang lebih baik atas resource sistem.

Memahami konsekuensi

Alarm berulang adalah mekanisme yang relatif sederhana dengan fleksibilitas yang terbatas. Alarm mungkin bukan pilihan terbaik untuk aplikasi Anda, terutama jika Anda perlu memicu operasi jaringan. Alarm yang tidak didesain dengan baik dapat menghabiskan baterai dan memberikan beban berat pada server.

Skenario umum untuk memicu operasi di luar masa aktif aplikasi adalah saat menyinkronkan data dengan server. Dalam kasus ini Anda mungkin berminat untuk menggunakan alarm berulang. Namun, jika Anda adalah pemilik dari server yang menghosting data aplikasi Anda, penggunaan Google Cloud Messaging (GCM) bersama dengan adaptor sinkronisasi adalah solusi yang lebih baik dari AlarmManager. Adaptor sinkronisasi memberi Anda semua opsi penjadwalan yang sama dengan AlarmManager, tetapi memberikan fleksibilitas yang jauh lebih tinggi. Misalnya, sinkronisasi dapat didasarkan pada pesan "data baru" dari server/perangkat (buka Menjalankan Adaptor Sinkronisasi untuk mengetahui detailnya), adanya (atau tidak adanya) aktivitas pengguna, waktu, dan masih banyak lagi. Buka video yang ditautkan di bagian atas halaman ini untuk mengetahui diskusi mendetail terkait waktu dan cara penggunaan GCM dan adaptor sinkronisasi.

Alarm tidak akan terpicu saat perangkat menganggur dalam mode Istirahatkan. Semua alarm yang dijadwalkan akan ditunda sampai perangkat tidak lagi dalam mode Istirahatkan. Jika Anda perlu memastikan bahwa pekerjaan Anda selesai bahkan saat perangkat sedang menganggur, ada beberapa opsi yang tersedia. Anda dapat menggunakan setAndAllowWhileIdle() atau setExactAndAllowWhileIdle() untuk menjamin bahwa alarm akan dijalankan. Opsi lainnya adalah menggunakan WorkManager API baru, yang dibuat untuk melakukan pekerjaan latar belakang baik satu kali maupun secara berkala. Untuk mengetahui informasi lebih lanjut, buka Menjadwalkan tugas dengan WorkManager.

Tips

Setiap pilihan yang Anda buat dalam mendesain alarm berulang dapat memiliki konsekuensi terhadap cara aplikasi menggunakan (atau menyalahgunakan) resource sistem. Misalnya, bayangkan suatu aplikasi populer yang disinkronkan dengan server. Jika operasi sinkronisasi didasarkan pada waktu jam dan setiap instance aplikasi disinkronkan pada pukul 23.00, beban terhadap server dapat menyebabkan latensi yang tinggi atau bahkan "denial of service". Ikuti praktik terbaik penggunaan alarm berikut:

  • Tambahkan pengacakan (jitter) untuk semua permintaan jaringan yang terpicu akibat alarm berulang:
    • Lakukan semua pekerjaan lokal saat alarm terpicu. "Pekerjaan lokal" berarti semua pekerjaan yang tidak terhubung ke server atau memerlukan data dari server.
    • Pada saat yang bersamaan, jadwalkan alarm yang berisi permintaan jaringan agar dipicu pada jangka waktu yang acak.
  • Pertahankan agar frekuensi alarm Anda tetap minimum.
  • Jangan aktifkan perangkat jika tidak perlu (perilaku ini ditentukan oleh jenis alarm, sebagaimana yang dijelaskan di Memilih jenis alarm).
  • Jangan membuat waktu pemicu alarm yang lebih tepat dari yang diperlukan.

    Gunakan setInexactRepeating(), dan bukan setRepeating(). Saat Anda menggunakan setInexactRepeating(), Android menyinkronkan alarm berulang dari beberapa aplikasi dan memicunya di saat yang bersamaan. Tindakan ini akan mengurangi jumlah total waktu yang digunakan sistem untuk mengaktifkan perangkat, sehingga mengurangi pemborosan baterai. Untuk Android versi 4.4 (API Level 19), semua alarm berulang tidak pasti. Perlu diperhatikan bahwa meskipun setInexactRepeating() merupakan peningkatan dari setRepeating(), tetapi kode tersebut masih dapat membebani server jika setiap instance aplikasi menghubungkan ke server di saat yang hampir bersamaan. Oleh karena itu, untuk permintaan jaringan, tambahkan beberapa pengacakan ke alarm Anda, seperti yang dijelaskan di atas.

  • Jika memungkinkan, hindari mendasarkan alarm pada waktu jam.

    Alarm berulang yang didasarkan pada waktu pemicu yang tepat tidak akan diskalakan dengan baik. Jika bisa, gunakan ELAPSED_REALTIME. Berbagai jenis alarm dijelaskan secara lebih mendetail di bagian berikut.

Menyetel alarm berulang

Seperti yang dijelaskan di atas, alarm berulang merupakan pilihan yang baik untuk menjadwalkan peristiwa rutin atau pencarian data. Alarm berulang memiliki karakteristik berikut:

  • Jenis alarm. Untuk pembahasan lebih lanjut, buka Memilih jenis alarm.
  • Waktu pemicu. Jika waktu pemicu yang Anda tentukan adalah di masa lalu, alarm akan langsung terpicu.
  • Interval alarm. Misalnya, sehari sekali, setiap jam, setiap 5 menit, dan sebagainya.
  • Intent tertunda yang terpicu saat alarm dipicu. Saat Anda menyetel alarm kedua yang menggunakan intent tertunda yang sama, alarm tersebut akan menggantikan alarm yang asli.

Untuk membatalkan PendingIntent, teruskan FLAG_NO_CREATE ke PendingIntent.getService() untuk mendapatkan instance intent (jika ada), lalu teruskan intent tersebut ke AlarmManager.cancel():

Kotlin

    val alarmManager =
        context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
    val pendingIntent =
        PendingIntent.getService(context, requestId, intent,
                                    PendingIntent.FLAG_NO_CREATE)
    if (pendingIntent != null && alarmManager != null) {
      alarmManager.cancel(pendingIntent)
    }
    

Java

    AlarmManager alarmManager =
        (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    PendingIntent pendingIntent =
        PendingIntent.getService(context, requestId, intent,
                                    PendingIntent.FLAG_NO_CREATE);
    if (pendingIntent != null && alarmManager != null) {
      alarmManager.cancel(pendingIntent);
    }
    

Memilih jenis alarm

Salah satu pertimbangan awal dalam menggunakan alarm berulang adalah jenisnya.

Ada dua jenis jam umum untuk alarm: "elapsed real time" dan "real time clock" (RTC). Elapsed real time menggunakan "time since system boot" sebagai referensi, dan real time clock menggunakan waktu UTC (jam dinding). Artinya, elapsed real time cocok untuk menyetel alarm berdasarkan waktu yang berlalu (misalnya, alarm yang terpicu setiap 30 detik) karena jenis alarm ini tidak terpengaruh oleh zona waktu/lokal. Jenis real time clock lebih cocok untuk alarm yang bergantung pada lokal saat ini.

Kedua jenis tersebut memiliki versi "wakeup", yang memberi tahu agar CPU perangkat diaktifkan jika layar nonaktif. Ini akan memastikan bahwa alarm dipicu pada waktu yang dijadwalkan. Ini bermanfaat jika aplikasi Anda memiliki dependensi waktu—misalnya, jika aplikasi memiliki jendela yang terbatas untuk melakukan operasi tertentu. Jika tidak menggunakan versi wakeup dari jenis alarm Anda, maka semua alarm berulang akan terpicu saat perangkat aktif di waktu berikutnya.

Jika Anda hanya memerlukan agar alarm terpicu pada interval tertentu, (misalnya setiap setengah jam), gunakan salah satu jenis elapsed real time. Secara umum, ini adalah pilihan yang lebih baik.

Jika Anda perlu agar alarm terpicu pada waktu tertentu dalam sehari, maka pilih salah satu jenis real time clock yang berbasis jam. Namun, perlu diperhatikan bahwa pendekatan ini dapat memiliki beberapa kekurangan—aplikasi mungkin tidak diterjemahkan dengan baik ke lokal lain, dan jika pengguna mengubah setelan waktu perangkat, tindakan tersebut dapat menyebabkan perilaku yang tidak diharapkan di aplikasi. Penggunaan jenis alarm real time clock juga tidak diskalakan dengan baik, seperti yang dijelaskan di atas. Jika memungkinkan, sebaiknya gunakan alarm "elapsed real time".

Berikut daftar jenis alarm:

  • ELAPSED_REALTIME—Memicu intent tertunda berdasarkan berdasarkan waktu yang berlalu sejak perangkat di-booting, tetapi tidak membangunkan perangkat. Waktu berlalu termasuk kapan saja saat perangkat sedang tidur.
  • ELAPSED_REALTIME_WAKEUP—Mengaktifkan perangkat dan memicu intent tertunda setelah jangka waktu yang ditentukan telah berlalu sejak perangkat di-booting.
  • RTC—Memicu intent tertunda pada waktu yang ditentukan, tetapi tidak mengaktifkan perangkat.
  • RTC_WAKEUP—Mengaktifkan perangkat untuk memicu intent tertunda pada waktu yang ditentukan.

Contoh alarm elapsed real time

Berikut beberapa contoh penggunaan ELAPSED_REALTIME_WAKEUP.

Mengaktifkan perangkat untuk memicu alarm dalam 30 menit, dan setiap 30 menit setelahnya:

Kotlin

    // Hopefully your alarm will have a lower frequency than this!
    alarmMgr?.setInexactRepeating(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
            AlarmManager.INTERVAL_HALF_HOUR,
            alarmIntent
    )
    

Java

    // Hopefully your alarm will have a lower frequency than this!
    alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
            AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);
    

Mengaktifkan perangkat untuk memicu alarm satu kali (tidak berulang) dalam satu menit:

Kotlin

    private var alarmMgr: AlarmManager? = null
    private lateinit var alarmIntent: PendingIntent
    ...
    alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
        PendingIntent.getBroadcast(context, 0, intent, 0)
    }

    alarmMgr?.set(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + 60 * 1000,
            alarmIntent
    )
    

Java

    private AlarmManager alarmMgr;
    private PendingIntent alarmIntent;
    ...
    alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, AlarmReceiver.class);
    alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

    alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() +
            60 * 1000, alarmIntent);
    

Contoh alarm real time clock

Berikut adalah beberapa contoh penggunaan RTC_WAKEUP.

Mengaktifkan perangkat untuk memicu alarm kira-kira pada pukul 14.00, kemudian mengulanginya sekali sehari di waktu yang sama:

Kotlin

    // Set the alarm to start at approximately 2:00 p.m.
    val calendar: Calendar = Calendar.getInstance().apply {
        timeInMillis = System.currentTimeMillis()
        set(Calendar.HOUR_OF_DAY, 14)
    }

    // With setInexactRepeating(), you have to use one of the AlarmManager interval
    // constants--in this case, AlarmManager.INTERVAL_DAY.
    alarmMgr?.setInexactRepeating(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            AlarmManager.INTERVAL_DAY,
            alarmIntent
    )
    

Java

    // Set the alarm to start at approximately 2:00 p.m.
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.HOUR_OF_DAY, 14);

    // With setInexactRepeating(), you have to use one of the AlarmManager interval
    // constants--in this case, AlarmManager.INTERVAL_DAY.
    alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
            AlarmManager.INTERVAL_DAY, alarmIntent);
    

Mengaktifkan perangkat untuk memicu alarm tepat pada pukul 08.30, dan setiap 20 menit setelahnya:

Kotlin

    private var alarmMgr: AlarmManager? = null
    private lateinit var alarmIntent: PendingIntent
    ...
    alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
        PendingIntent.getBroadcast(context, 0, intent, 0)
    }

    // Set the alarm to start at 8:30 a.m.
    val calendar: Calendar = Calendar.getInstance().apply {
        timeInMillis = System.currentTimeMillis()
        set(Calendar.HOUR_OF_DAY, 8)
        set(Calendar.MINUTE, 30)
    }

    // setRepeating() lets you specify a precise custom interval--in this case,
    // 20 minutes.
    alarmMgr?.setRepeating(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            1000 * 60 * 20,
            alarmIntent
    )
    

Java

    private AlarmManager alarmMgr;
    private PendingIntent alarmIntent;
    ...
    alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, AlarmReceiver.class);
    alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

    // Set the alarm to start at 8:30 a.m.
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.HOUR_OF_DAY, 8);
    calendar.set(Calendar.MINUTE, 30);

    // setRepeating() lets you specify a precise custom interval--in this case,
    // 20 minutes.
    alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
            1000 * 60 * 20, alarmIntent);
    

Menentukan seberapa tepat alarm Anda seharusnya

Seperti yang dijelaskan di atas, memilih jenis alarm sering kali merupakan langkah pertama dalam pembuatan alarm. Perbedaan lebih lanjut adalah seberapa tepat alarm Anda seharusnya. Untuk sebagian besar aplikasi, setInexactRepeating() adalah pilihan yang tepat. Saat Anda menggunakan metode ini, Android menyinkronkan beberapa alarm berulang tidak pasti dan memicunya di waktu yang bersamaan. Ini akan mengurangi pemborosan baterai.

Untuk aplikasi langka yang memiliki persyaratan waktu yang kaku—misalnya, alarm harus dipicu tepat pada pukul 08.30, dan setiap satu jam setelahnya—gunakan setRepeating(). Namun, jika memungkinkan, sebaiknya hindari penggunaan alarm yang tepat.

Dengan setInexactRepeating(), Anda tidak dapat menentukan interval khusus seperti yang dapat Anda lakukan dengan setRepeating(). Anda harus menggunakan salah satu konstanta interval, seperti INTERVAL_FIFTEEN_MINUTES, INTERVAL_DAY, dan sebagainya. Buka AlarmManager untuk melihat daftar lengkapnya.

Membatalkan alarm

Bergantung pada aplikasi, Anda mungkin ingin menyertakan kemampuan membatalkan alarm. Untuk membatalkan alarm, panggil cancel() di Alarm Manager, yang meneruskan PendingIntent yang tidak ingin Anda picu. Contoh:

Kotlin

    // If the alarm has been set, cancel it.
    alarmMgr?.cancel(alarmIntent)
    

Java

    // If the alarm has been set, cancel it.
    if (alarmMgr!= null) {
        alarmMgr.cancel(alarmIntent);
    }
    

Memulai alarm saat perangkat dimulai ulang

Secara default, semua alarm dibatalkan setelah perangkat nonaktif. Untuk mencegah agar hal ini tidak terjadi, Anda dapat mendesain aplikasi agar secara otomatis memulai ulang alarm berulang jika pengguna melakukan reboot pada perangkat. Tindakan ini memastikan bahwa AlarmManager akan terus melakukan tugasnya agar pengguna tidak perlu memulai ulang alarm secara manual.

Berikut langkah-langkahnya:

  1. Tetapkan izin RECEIVE_BOOT_COMPLETED dalam manifes aplikasi Anda. Ini akan memungkinkan aplikasi Anda menerima ACTION_BOOT_COMPLETED yang disiarkan setelah sistem menyelesaikan booting (ini hanya berfungsi jika aplikasi telah diluncurkan oleh pengguna minimal satu kali):
        <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
  2. Implementasikan BroadcastReceiver untuk menerima siaran:

    Kotlin

        class SampleBootReceiver : BroadcastReceiver() {
    
            override fun onReceive(context: Context, intent: Intent) {
                if (intent.action == "android.intent.action.BOOT_COMPLETED") {
                    // Set the alarm here.
                }
            }
        }
        

    Java

        public class SampleBootReceiver extends BroadcastReceiver {
    
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
                    // Set the alarm here.
                }
            }
        }
        
  3. Tambahkan penerima ke file manifes aplikasi Anda dengan filter intent yang memfilter di tindakan ACTION_BOOT_COMPLETED:
    <receiver android:name=".SampleBootReceiver"
                android:enabled="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"></action>
            </intent-filter>
        </receiver>

    Perhatikan bahwa di manifes, penerima booting disetel ke android:enabled="false". Artinya, penerima tidak akan dipanggil kecuali aplikasi secara eksplisit mengaktifkannya. Ini akan mencegah agar penerima booting tidak dipanggil jika tidak diperlukan. Anda dapat mengaktifkan penerima (misalnya, jika pengguna menyetel alarm) seperti berikut:

    Kotlin

        val receiver = ComponentName(context, SampleBootReceiver::class.java)
    
        context.packageManager.setComponentEnabledSetting(
                receiver,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP
        )
        

    Java

        ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
        PackageManager pm = context.getPackageManager();
    
        pm.setComponentEnabledSetting(receiver,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);
        

    Setelah Anda mengaktifkan penerima dengan cara ini, penerima akan terus diaktifkan, meskipun pengguna melakukan reboot pada perangkat. Dengan kata lain, pengaktifan penerima melalui program akan menggantikan setelan manifes, bahkan saat perangkat di-reboot. Penerima akan tetap aktif sampai aplikasi Anda menonaktifkannya. Anda dapat menonaktifkan penerima (misalnya, jika pengguna membatalkan alarm) seperti berikut:

    Kotlin

        val receiver = ComponentName(context, SampleBootReceiver::class.java)
    
        context.packageManager.setComponentEnabledSetting(
                receiver,
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP
        )
        

    Java

        ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
        PackageManager pm = context.getPackageManager();
    
        pm.setComponentEnabledSetting(receiver,
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP);
        

Dampak fitur Istirahatkan dan Aplikasi Standby

Fitur Istirahatkan dan Aplikasi Standby diperkenalkan di Android versi 6.0 (API level 23) sebagai upaya untuk memperpanjang masa pakai baterai. Saat perangkat berada dalam mode Istirahatkan, semua alarm standar akan ditunda sampai perangkat keluar dari mode Istirahatkan atau sampai masa pemeliharaan terbuka. Jika ada alarm yang harus terpicu bahkan dalam mode Istirahatkan, Anda dapat menggunakan setAndAllowWhileIdle() atau setExactAndAllowWhileIdle(). Aplikasi Anda akan memasuki mode Aplikasi Standby saat menganggur, yang berarti bahwa pengguna tidak menggunakannya selama jangka waktu tertentu dan aplikasi tidak memiliki proses latar depan. Saat aplikasi berada dalam mode Aplikasi Standby, alarm ditunda seperti dalam mode Istirahatkan. Batasan ini dicabut saat aplikasi tidak lagi menganggur atau jika perangkat dicolokkan ke suplai daya. Untuk informasi selengkapnya terkait dampak mode ini terhadap aplikasi Anda, baca Mengoptimalkan Aplikasi untuk Mode Istirahatkan dan Aplikasi Standby.