Anda tidak bisa bertanya kepada Android apakah VPN terhubung. VpnService tidak mengekspos getter koneksi, dan ikon kunci di bilah status digambar oleh sistem, bukan oleh sesuatu yang bisa dibaca aplikasi Anda. Yang bisa Anda lakukan adalah mengajukan pertanyaan yang lebih sempit kepada tumpukan konektivitas: jaringan mana yang dipakai lalu lintas aplikasi ini, dan apakah jaringan itu membawa transport VPN. Runbook ini menelusuri pemeriksaan dalam urutan yang menghindari positif palsu, dari perintah shell di perangkat hingga callback yang Anda biarkan berjalan di aplikasi.

Periksa perangkat dari shell sebelum menyentuh kode aplikasi

Dapatkan garis dasar dulu. adb shell dumpsys connectivity mencetak setiap jaringan yang dikenal sistem, dan setiap blok mencantumkan transportnya, sehingga jaringan VPN muncul dengan VPN dalam daftar itu. Baca blok jaringan default sebelum apa pun, lalu cari jaringan lain yang membawa transport yang sama.

Dua keluaran menjawab pertanyaan berbeda. Ada atau tidaknya jaringan VPN tidak sama dengan apakah aplikasi Anda memakainya, dan pada perangkat dengan profil kerja atau pengguna kedua, keduanya bisa benar sekaligus sementara proses Anda berbicara langsung ke internet. Baris jaringan aktif adalah yang menentukan apa yang dilakukan soket Anda.

Mintalah jaringan yang dipakai aplikasi

Di kode, uji kemampuan itu empat baris. Minta ACCESS_NETWORK_STATE di manifes, lalu bandingkan kemampuan jaringan aktif dengan transport VPN:

ConnectivityManager cm = (ConnectivityManager)
    context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean vpnInUse = caps != null
    && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

Jawabannya sengaja sempit: apakah jaringan default aplikasi adalah VPN saat ini. Itu tidak mengatakan apa pun tentang VPN mana, apakah milik Anda, atau apakah terowongannya mencapai ujung jauhnya. Perlakukan true sebagai “lalu lintas keluar melalui suatu terowongan” dan simpan pembukuan Anda sendiri secara terpisah. Boolean yang diam-diam berarti tiga hal sekaligus adalah cara layar pengaturan akhirnya melaporkan VPN mati sementara pengguna menjelajah melaluinya.

Ketika sebaliknya Anda perlu tahu apakah ada jaringan VPN apa pun, iterasi cm.getAllNetworks() dan uji setiap set kemampuan. Kedua jawaban berbeda pada perangkat yang menjalankan VPN per aplikasi yang mencakup aplikasi lain tetapi bukan milik Anda, dan perbedaan itulah sumber sebagian besar laporan bug yang mengatakan aplikasi tidak melihat VPN yang jelas-jelas menyala.

Pantau perubahan alih-alih melakukan polling

Polling dengan pengatur waktu adalah bentuk yang salah untuk pertanyaan ini, karena perubahan status datang sebagai peristiwa. Daftarkan permintaan yang cocok dengan transport VPN dan biarkan framework memberi tahu Anda kapan jaringan yang cocok muncul, hilang, atau berubah:

NetworkRequest request = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();
cm.registerNetworkCallback(request, callback);

Jaringan VPN tidak pernah membawa NET_CAPABILITY_NOT_VPN, jadi penghapusan eksplisit mendokumentasikan maksudnya. Tanpanya permintaan tetap cocok, tetapi orang berikutnya yang membaca kode harus menyimpulkannya sendiri.

Pendaftaran milik cakupan yang membutuhkan pengamatan, biasanya sebuah layar atau layanan latar depan. Batalkan pendaftaran di onStop atau onDestroy, dan jangan tinggalkan callback menempel pada activity yang sudah hilang. Aplikasi tanpa komponen terlihat adalah tuan rumah buruk bagi pengamat permanen, dan callback yang Anda lupakan adalah yang crash setelah perubahan konfigurasi.

Pisahkan tiga pertanyaan

Sebagian besar kebingungan di area ini berasal dari memperlakukan tiga pemeriksaan berbeda sebagai satu. Mereka memakai API berbeda, dan masing-masing bisa benar sementara yang lain salah.

QuestionHow you answer itWhat a yes means
Did the user grant VPN consent to my app?VpnService.prepare() returns nullConsent exists; the tunnel may still be down
Is the device sending default traffic through a VPN?TRANSPORT_VPN on the active networkSomething is tunneling, not necessarily your app
Did this request arrive through the VPN?Source address seen by the serverProof for that connection, nothing wider

Baris ketiga adalah yang coba dijawab orang di klien, dan itu tidak bisa dijawab di sana. Beri klien VPN kumpulan alamatnya sendiri dan baca alamat sumber di server; itulah satu-satunya bukti yang bertahan ketika pengguna berganti terowongan di tengah sesi.

Urutan verifikasi yang menyingkirkan positif palsu

Kode contoh lama yang masih beredar memanggil getNetworkInfo(ConnectivityManager.TYPE_VPN). Panggilan itu usang, dan pada beberapa rilis ia mengembalikan null bahkan saat VPN terhubung, itulah sebabnya banyak cuplikan yang disalin tampak rusak di perangkat yang berfungsi. Pemeriksaan kemampuan menghindari jebakan itu dan berperilaku sama sejak Android 5.0. Jika Anda memelihara basis kode yang masih memanggilnya, ganti panggilannya alih-alih membungkusnya dalam fallback, karena fallback-lah yang menghasilkan jawaban salah sejak awal.

Satu jalan pintas layak diberi peringatan. Membandingkan alamat publik yang dilaporkan layanan eksternal dengan alamat keluar VPN memang membuktikan sesuatu tentang jalurnya, tetapi menambahkan permintaan pihak ketiga ke setiap pemeriksaan, bergantung pada layanan itu tetap hidup, dan bisa di-cache atau diblokir. Simpan sebagai diagnostik manual, jangan pernah sebagai pemeriksaan status di dalam aplikasi.

Jalankan pemeriksaan dalam urutan ini dan berhenti pada yang pertama yang tidak cocok dengan laporan pengguna: daftar transport di shell, kemampuan jaringan aktif, peristiwa callback saat Anda menyalakan dan mematikan terowongan, lalu alamat sumber di sisi server. Jika keempatnya cocok dan aplikasi masih berperilaku buruk, bug-nya ada di mesin status Anda sendiri, bukan di pandangan Android tentang jaringan.

Artikel terkait