Membuat dan memantau pembatasan wilayah

Pembatasan wilayah menggabungkan kemampuan penentuan lokasi pengguna saat ini dan kemampuan penentuan kedekatan lokasi pengguna dengan lokasi tertentu. Untuk menandai lokasi tertentu, Anda harus menentukan lintang dan bujurnya. Untuk menyesuaikan kedekatan lokasi, Anda harus menambahkan radius. Lintang, bujur, dan radius menentukan pembatasan wilayah, yang membuat area melingkar, atau pembatas, di sekitar lokasi tertentu.

Anda dapat memiliki beberapa pembatasan wilayah aktif, dengan batas 100 per pengguna perangkat di semua aplikasi. Untuk setiap pembatasan wilayah, Anda dapat meminta Layanan Lokasi untuk mengirimi Anda peristiwa masuk dan keluar, atau menentukan durasi di area pembatasan wilayah untuk menunggu, atau diam, sebelum memicu suatu peristiwa. Anda dapat membatasi durasi pembatasan wilayah dengan menentukan durasi akhir dalam milidetik. Setelah pembatasan wilayah berakhir, Layanan Lokasi secara otomatis akan menghapusnya.

Tutorial ini menunjukkan cara menambahkan dan menghapus pembatasan wilayah, kemudian memproses transisi pembatasan wilayah menggunakan BroadcastReceiver.

Menyiapkan pemantauan pembatasan wilayah

Langkah pertama dalam meminta pemantauan pembatasan wilayah adalah meminta izin yang diperlukan. Untuk menggunakan pembatasan wilayah, aplikasi Anda harus meminta ACCESS_FINE_LOCATION. Jika aplikasi Anda menargetkan Android 10 (API level 29) atau versi lebih tinggi, aplikasi tersebut juga harus meminta ACCESS_BACKGROUND_LOCATION.

Untuk meminta izin yang diperlukan, tambahkan sebagai elemen turunan dari elemen <manifest> dalam manifes aplikasi Anda:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

    <!-- Required if your app targets Android 10 (API level 29) or higher -->
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
    

Jika Anda ingin menggunakan BroadcastReceiver untuk memproses transisi pembatasan wilayah, tambahkan elemen yang menentukan nama layanan. Elemen ini harus berupa turunan elemen <application>:

    <application
       android:allowBackup="true">
       ...
       <receiver android:name=".GeofenceBroadcastReceiver"/>
    <application/>
    

Untuk mengakses API lokasi, Anda perlu membuat instance klien Pembatasan Wilayah. Untuk mempelajari cara menghubungkan klien Anda:

Kotlin

    lateinit var geofencingClient: GeofencingClient

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        geofencingClient = LocationServices.getGeofencingClient(this)
    }
    

Java

    private GeofencingClient geofencingClient;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // ...
        geofencingClient = LocationServices.getGeofencingClient(this);
    }
    

Membuat dan menambahkan pembatasan wilayah

Aplikasi Anda perlu membuat dan menambahkan pembatasan wilayah menggunakan class builder API lokasi untuk membuat objek Pembatasan Wilayah, dan class praktis untuk menambahkannya. Selain itu, untuk menangani intent yang dikirim dari Layanan Lokasi saat transisi Pembatasan Wilayah terjadi, Anda dapat menentukan PendingIntent seperti yang ditunjukkan dalam bagian ini.

Catatan: Pada perangkat pengguna tunggal, ada batas sebanyak 100 pembatasan wilayah per aplikasi. Untuk perangkat multi-pengguna, ada batas sebanyak 100 pembatasan wilayah per aplikasi per pengguna perangkat.

Membuat objek pembatasan wilayah

Pertama, gunakan Geofence.Builder untuk membuat pembatasan wilayah, yang menyetel jenis transisi, durasi dan radius untuk pembatasan wilayah. Misalnya, untuk mengisi objek daftar:

Kotlin

    geofenceList.add(Geofence.Builder()
            // Set the request ID of the geofence. This is a string to identify this
            // geofence.
            .setRequestId(entry.key)

            // Set the circular region of this geofence.
            .setCircularRegion(
                    entry.value.latitude,
                    entry.value.longitude,
                    Constants.GEOFENCE_RADIUS_IN_METERS
            )

            // Set the expiration duration of the geofence. This geofence gets automatically
            // removed after this period of time.
            .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)

            // Set the transition types of interest. Alerts are only generated for these
            // transition. We track entry and exit transitions in this sample.
            .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)

            // Create the geofence.
            .build())
    

Java

    geofenceList.add(new Geofence.Builder()
        // Set the request ID of the geofence. This is a string to identify this
        // geofence.
        .setRequestId(entry.getKey())

        .setCircularRegion(
                entry.getValue().latitude,
                entry.getValue().longitude,
                Constants.GEOFENCE_RADIUS_IN_METERS
        )
        .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
        .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
                Geofence.GEOFENCE_TRANSITION_EXIT)
        .build());
    

Contoh ini menarik data dari file konstanta. Dalam praktik nyata, aplikasi mungkin secara dinamis akan membuat pembatasan wilayah berdasarkan lokasi pengguna.

Menentukan pembatasan wilayah dan pemicu awal

Cuplikan berikut menggunakan class GeofencingRequest dan class GeofencingRequestBuilder yang disarangkan untuk menentukan pembatasan wilayah yang akan dipantau dan untuk menyetel cara memicu peristiwa pembatasan wilayah terkait:</a>

Kotlin

    private fun getGeofencingRequest(): GeofencingRequest {
        return GeofencingRequest.Builder().apply {
            setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            addGeofences(geofenceList)
        }.build()
    }
    

Java

    private GeofencingRequest getGeofencingRequest() {
        GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
        builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
        builder.addGeofences(geofenceList);
        return builder.build();
    }
    

Contoh berikut menunjukkan penggunaan dua pemicu pembatasan wilayah. Transisi GEOFENCE_TRANSITION_ENTER dipicu saat perangkat memasuki pembatasan wilayah, dan transisi GEOFENCE_TRANSITION_EXIT dipicu saat perangkat keluar dari pembatasan wilayah. Menentukan INITIAL_TRIGGER_ENTER akan memberi tahu layanan Lokasi bahwa GEOFENCE_TRANSITION_ENTER harus dipicu jika perangkat sudah berada di dalam pembatasan wilayah.

Dalam banyak kasus, mungkin lebih baik gunakan INITIAL_TRIGGER_DWELL, yang memicu peristiwa hanya saat pengguna berhenti selama durasi yang ditentukan dalam pembatasan wilayah. Pendekatan ini dapat membantu mengurangi "notifikasi spam" yang dihasilkan dari notifikasi dalam jumlah banyak saat perangkat masuk dan keluar sebentar dari pembatasan wilayah. Strategi lain untuk mendapatkan hasil terbaik dari pembatasan wilayah Anda adalah menetapkan radius minimum sejauh 100 meter. Cara ini membantu memperhitungkan akurasi lokasi jaringan Wi-Fi biasa, dan juga membantu mengurangi konsumsi daya perangkat.

Menentukan penerima siaran untuk transisi pembatasan wilayah

Suatu Intent yang dikirim dari Layanan Lokasi dapat memicu berbagai tindakan dalam aplikasi, tetapi Anda tidak boleh membiarkannya memulai aktivitas atau fragmen karena komponen seharusnya terlihat sebagai respons terhadap tindakan pengguna. Dalam banyak kasus, BroadcastReceiver adalah cara yang baik untuk menangani transisi pembatasan wilayah. Suatu BroadcastReceiver akan mendapatkan update saat suatu peristiwa terjadi, seperti transisi masuk atau keluar dari pembatasan wilayah, dan dapat memulai pekerjaan di latar belakang yang sudah berjalan dalam waktu lama.

Cuplikan berikut menunjukkan cara menentukan PendingIntent yang memulai suatu BroadcastReceiver:

Kotlin

    class MainActivity : AppCompatActivity() {

        // ...

        private val geofencePendingIntent: PendingIntent by lazy {
            val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
            // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
            // addGeofences() and removeGeofences().
            PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        }
    }
    

Java

    public class MainActivity extends AppCompatActivity {

        // ...

        private PendingIntent getGeofencePendingIntent() {
            // Reuse the PendingIntent if we already have it.
            if (geofencePendingIntent != null) {
                return geofencePendingIntent;
            }
            Intent intent = new Intent(this, GeofenceBroadcastReceiver.class);
            // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
            // calling addGeofences() and removeGeofences().
            geofencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.
                    FLAG_UPDATE_CURRENT);
            return geofencePendingIntent;
        }
    

Menambahkan pembatasan wilayah

Untuk menambahkan pembatasan wilayah, gunakan metode GeofencingClient.addGeofences(). Berikan objek GeofencingRequest, dan PendingIntent. Cuplikan berikut menunjukkan pemrosesan hasilnya:

Kotlin

    geofencingClient?.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run {
        addOnSuccessListener {
            // Geofences added
            // ...
        }
        addOnFailureListener {
            // Failed to add geofences
            // ...
        }
    }
    

Java

    geofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent())
            .addOnSuccessListener(this, new OnSuccessListener<Void>() {
                @Override
                public void onSuccess(Void aVoid) {
                    // Geofences added
                    // ...
                }
            })
            .addOnFailureListener(this, new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // Failed to add geofences
                    // ...
                }
            });
    

Menangani transisi pembatasan wilayah

Ketika Layanan Lokasi mendeteksi bahwa pengguna telah memasuki atau keluar dari pembatasan wilayah, Intent yang ada di dalam PendingIntent yang Anda sertakan dalam permintaan untuk menambahkan pembatasan wilayah akan dikirimkan. Penerima siaran seperti GeofenceBroadcastReceiver mengetahui bahwa Intent dipanggil, kemudian dapat memperoleh peristiwa pembatasan wilayah dari intent, menentukan jenis transisi Pembatasan Wilayah, serta menentukan pembatasan wilayah yang sudah ditetapkan mana yang dipicu. Penerima siaran dapat mengarahkan aplikasi agar mulai menjalankan pekerjaan di latar belakang atau, jika diinginkan, mengirim notifikasi sebagai output.

Catatan: Di Android 8.0 (API level 26) dan versi lebih tinggi, jika aplikasi berjalan di latar belakang sambil memantau pembatasan wilayah, perangkat akan merespons peristiwa pembatasan wilayah setiap beberapa menit. Untuk mempelajari cara menyesuaikan aplikasi Anda dengan batas respons ini, baca Batas Lokasi Latar Belakang.

Cuplikan berikut menunjukkan cara menentukan BroadcastReceiver yang memposting notifikasi jika terjadi transisi pembatasan wilayah. Saat pengguna mengklik notifikasi, aktivitas utama aplikasi akan muncul:

Kotlin

    class GeofenceBroadcastReceiver : BroadcastReceiver() {
        // ...
        override fun onReceive(context: Context?, intent: Intent?) {
            val geofencingEvent = GeofencingEvent.fromIntent(intent)
            if (geofencingEvent.hasError()) {
                val errorMessage = GeofenceErrorMessages.getErrorString(this,
                        geofencingEvent.errorCode)
                Log.e(TAG, errorMessage)
                return
            }

            // Get the transition type.
            val geofenceTransition = geofencingEvent.geofenceTransition

            // Test that the reported transition was of interest.
            if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER |
                    geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {

                // Get the geofences that were triggered. A single event can trigger
                // multiple geofences.
                val triggeringGeofences = geofencingEvent.triggeringGeofences

                // Get the transition details as a String.
                val geofenceTransitionDetails = getGeofenceTransitionDetails(
                        this,
                        geofenceTransition,
                        triggeringGeofences
                )

                // Send notification and log the transition details.
                sendNotification(geofenceTransitionDetails)
                Log.i(TAG, geofenceTransitionDetails)
            } else {
                // Log the error.
                Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
                        geofenceTransition))
            }
        }
    }
    

Java

    public class GeofenceBroadcastReceiver extends BroadcastReceiver {
        // ...
        protected void onReceive(Context context, Intent intent) {
            GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
            if (geofencingEvent.hasError()) {
                String errorMessage = GeofenceErrorMessages.getErrorString(this,
                        geofencingEvent.getErrorCode());
                Log.e(TAG, errorMessage);
                return;
            }

            // Get the transition type.
            int geofenceTransition = geofencingEvent.getGeofenceTransition();

            // Test that the reported transition was of interest.
            if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
                    geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {

                // Get the geofences that were triggered. A single event can trigger
                // multiple geofences.
                List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();

                // Get the transition details as a String.
                String geofenceTransitionDetails = getGeofenceTransitionDetails(
                        this,
                        geofenceTransition,
                        triggeringGeofences
                );

                // Send notification and log the transition details.
                sendNotification(geofenceTransitionDetails);
                Log.i(TAG, geofenceTransitionDetails);
            } else {
                // Log the error.
                Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
                        geofenceTransition));
            }
        }
    

Setelah mendeteksi peristiwa transisi melalui PendingIntent, BroadcastReceiver akan mendapatkan jenis transisi pembatasan wilayah dan mengujinya apakah termasuk salah satu peristiwa yang digunakan aplikasi untuk memicu notifikasi, dalam hal ini baik GEOFENCE_TRANSITION_ENTER maupun GEOFENCE_TRANSITION_EXIT. Layanan tersebut kemudian mengirimkan notifikasi dan mencatat detail transisi.

Menghentikan pemantauan pembatasan wilayah

Penghentian pemantauan pembatasan wilayah saat tidak lagi diperlukan atau diinginkan dapat membantu menghemat daya baterai dan siklus CPU pada perangkat. Anda dapat menghentikan pemantauan pembatasan wilayah dalam aktivitas utama yang digunakan untuk menambahkan dan menghapus pembatasan wilayah; penghapusan pembatasan wilayah akan menghentikannya secara langsung. API menyediakan metode untuk menghapus pembatasan wilayah baik dengan ID permintaan, atau dengan menghapus pembatasan wilayah yang berkaitan dengan PendingIntent tertentu.

Cuplikan berikut menghapus pembatasan wilayah dengan PendingIntent, yang akan menghentikan semua notifikasi lebih lanjut ketika perangkat memasuki atau keluar dari pembatasan wilayah yang telah ditambahkan sebelumnya:

Kotlin

    geofencingClient?.removeGeofences(geofencePendingIntent)?.run {
        addOnSuccessListener {
            // Geofences removed
            // ...
        }
        addOnFailureListener {
            // Failed to remove geofences
            // ...
        }
    }
    

Java

    geofencingClient.removeGeofences(getGeofencePendingIntent())
            .addOnSuccessListener(this, new OnSuccessListener<Void>() {
                @Override
                public void onSuccess(Void aVoid) {
                    // Geofences removed
                    // ...
                }
            })
            .addOnFailureListener(this, new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // Failed to remove geofences
                    // ...
                }
            });
    

Anda dapat menggabungkan pembatasan wilayah dengan fitur kemampuan penentuan lokasi lainnya, seperti update lokasi berkala. Untuk informasi selengkapnya, lihat tutorial lain di kelas ini.

Menggunakan praktik terbaik untuk pembatasan wilayah

Bagian ini menguraikan rekomendasi untuk menggunakan pembatasan wilayah dengan API lokasi bagi Android.

Mengurangi konsumsi daya

Anda dapat menggunakan teknik berikut untuk mengoptimalkan konsumsi daya di aplikasi Anda yang menggunakan pembatasan wilayah:

  • Setel responsivitas notifikasi ke nilai yang lebih tinggi. Hal tersebut akan mengoptimalkan konsumsi daya dengan menambah latensi notifikasi pembatasan wilayah. Misalnya, jika Anda menyetel nilai respons selama lima menit, aplikasi Anda hanya akan memeriksa notifikasi masuk atau keluar satu kali setiap lima menit. Menyetel nilai yang lebih rendah tidak selalu berarti bahwa pengguna akan mendapat notifikasi dalam periode waktu tersebut (misalnya, jika Anda menyetel nilai 5 detik, mungkin butuh waktu sedikit lebih lama untuk menerima notifikasi).

  • Gunakan radius pembatasan wilayah yang lebih besar untuk lokasi tempat pengguna menghabiskan banyak waktu, seperti rumah atau kantor. Radius yang lebih besar tidak akan secara langsung mengurangi konsumsi daya, tetapi akan mengurangi frekuensi masuk atau keluar yang diperiksa oleh aplikasi, yang secara efektif akan menurunkan konsumsi daya secara keseluruhan.

Memilih radius optimal untuk pembatasan wilayah Anda

Untuk hasil terbaik, radius minimium pembatasan wilayah harus disetel antara 100 - 150 meter. Jika Wi-Fi tersedia, akurasi lokasi biasanya antara 20 - 50 meter. Jika lokasi dalam ruangan tersedia, rentang akurasi dapat sekecil 5 meter. Kecuali Anda tahu ada lokasi dalam ruangan yang tersedia di dalam pembatasan wilayah, anggaplah akurasi lokasi Wi-Fi sekitar 50 meter.

Jika lokasi Wi-Fi tidak tersedia (misalnya, ketika Anda mengemudi di daerah pedesaan) akurasi lokasi akan menurun. Rentang akurasi dapat mencapai beberapa ratus meter hingga beberapa kilometer. Dalam kasus seperti ini, Anda harus membuat pembatasan wilayah dengan radius yang lebih besar.

Menggunakan jenis transisi diam untuk mengurangi notifikasi spam

Jika Anda menerima banyak notifikasi saat berkendara sebentar melewati suatu pembatasan wilayah, cara terbaik untuk mengurangi notifikasi adalah dengan menggunakan jenis transisi GEOFENCE_TRANSITION_DWELL, bukan GEOFENCE_TRANSITION_ENTER. Dengan cara ini, notifikasi diam akan dikirim hanya saat pengguna berhenti di dalam pembatasan wilayah selama jangka waktu tertentu. Anda dapat memilih durasinya dengan menyetel penundaan loitering.

Mendaftarkan ulang pembatasan wilayah hanya jika diperlukan

Pembatasan wilayah terdaftar disimpan dalam proses com.google.process.location yang dimiliki oleh paket com.google.android.gms. Aplikasi tidak perlu melakukan apa pun untuk menangani peristiwa berikut, karena sistem akan mengembalikan pembatasan wilayah setelah peristiwa ini:

  • Layanan Google Play diupgrade.
  • Layanan Google Play dimatikan dan dimulai ulang oleh sistem karena pembatasan resource.
  • Proses lokasi terhenti.

Aplikasi harus mendaftarkan ulang pembatasan wilayah jika masih diperlukan setelah peristiwa berikut, karena sistem tidak dapat memulihkan pembatasan wilayah dalam kasus berikut:

  • Perangkat di-booting ulang. Aplikasi harus memroses tindakan booting lengkap perangkat, kemudian mendaftarkan ulang pembatasan wilayah yang diperlukan.
  • Aplikasi dihapus dan diinstal ulang.
  • Data aplikasi dihapus.
  • Data layanan Google Play dihapus.
  • Aplikasi menerima notifikasi GEOFENCE_NOT_AVAILABLE. Ini biasanya terjadi setelah NLP (Penyedia Lokasi Jaringan Android) dinonaktifkan.

Memecahkan masalah peristiwa masuk pembatasan wilayah

Jika pembatasan wilayah tidak dipicu saat perangkat memasuki pembatasan wilayah (notifikasi GEOFENCE_TRANSITION_ENTER tidak dipicu), terlebih dahulu pastikan bahwa pembatasan wilayah Anda terdaftar dengan benar seperti dijelaskan dalam panduan ini.

Berikut adalah beberapa kemungkinan alasan notifikasi tidak berfungsi seperti yang diharapkan:

  • Lokasi akurat tidak tersedia dalam pembatasan wilayah Anda atau pembatasan wilayah Anda terlalu kecil. Di sebagian besar perangkat, layanan pembatasan wilayah hanya menggunakan lokasi jaringan untuk memicu pembatasan wilayah. Layanan menggunakan pendekatan ini karena lokasi jaringan mengonsumsi daya jauh lebih sedikit, perlu waktu lebih sedikit untuk mendapatkan lokasi diskret, dan yang paling penting, tersedia di dalam ruangan.
  • Wi-Fi dimatikan pada perangkat. Mengaktifkan Wi-Fi dapat meningkatkan akurasi lokasi secara signifikan, sehingga jika Wi-Fi dimatikan, aplikasi Anda mungkin tidak akan pernah menerima notifikasi pembatasan wilayah, bergantung pada beberapa setelan termasuk radius pembatasan wilayah, model perangkat, atau versi Android. Mulai dari Android 4.3 (API level 18), kami menambahkan kemampuan "Mode hanya pemindaian Wi-Fi" yang memungkinkan pengguna untuk menonaktifkan Wi-Fi, tetapi masih mendapatkan lokasi jaringan yang baik. Ini adalah praktik yang baik untuk meminta pengguna dan memberikan pintasan bagi pengguna untuk mengaktifkan mode Wi-Fi atau hanya pemindaian Wi-Fi jika keduanya dinonaktifkan. Gunakan SettingsClient untuk memastikan bahwa setelan sistem perangkat telah dikonfigurasi dengan benar untuk deteksi lokasi yang optimal.</a>

    Catatan: Jika aplikasi Anda menargetkan Android 10 (API level 29) atau versi lebih tinggi, Anda tidak dapat memanggil WifiManager.setEnabled() secara langsung, kecuali aplikasi Anda adalah aplikasi sistem atau pengontrol kebijakan perangkat (DPC). Sebagai gantinya, gunakan panel setelan.

  • Tidak ada konektivitas jaringan yang andal dalam pembatasan wilayah Anda. Jika tidak ada koneksi data yang dapat diandalkan, notifikasi mungkin tidak dihasilkan. Hal ini disebabkan karena layanan pembatasan wilayah bergantung pada penyedia lokasi jaringan yang secara bergantian memerlukan koneksi data.
  • Notifikasi dapat muncul terlambat. Layanan pembatasan wilayah tidak terus-menerus mencari lokasi, jadi akan ada sedikit latensi saat menerima notifikasi. Biasanya latensi terjadi kurang dari 2 menit, bahkan kurang jika perangkat telah bergerak. Jika Batas Lokasi Latar Belakang berlaku, latensi rata-rata berlangsung sekitar 2-3 menit. Jika perangkat tidak bergerak selama periode waktu yang cukup lama, latensi dapat meningkat (hingga 6 menit).