Jak sprawdzić w kodzie, czy VPN jest połączony na Androidzie
Android nie ma gettera stanu połączenia VPN. Odpytuj możliwości sieci, obserwuj zmiany transportu i trzymaj osobno zgodę aplikacji, stan urządzenia oraz dowód po stronie serwera.
Nie możesz zapytać Androida, czy VPN jest połączony. VpnService nie udostępnia gettera stanu połączenia, a ikonę klucza na pasku stanu rysuje system, nie cokolwiek, co Twoja aplikacja może odczytać. Możesz natomiast zadać stosowi łączności węższe pytanie: której sieci używa ruch tej aplikacji i czy ta sieć niesie transport VPN. Ten poradnik prowadzi przez kontrole w kolejności, która pozwala uniknąć fałszywych trafień, od polecenia shell na urządzeniu po callback pozostawiony w aplikacji.
Sprawdź urządzenie z powłoki, zanim dotkniesz kodu aplikacji
Najpierw pobierz stan bazowy. adb shell dumpsys connectivity wypisuje każdą sieć znaną systemowi, a każdy blok wymienia swoje transporty, więc sieć VPN pojawia się z VPN na tej liście. Najpierw odczytaj blok sieci domyślnej, potem szukaj innych sieci niosących ten sam transport.
Dwa wyniki odpowiadają na różne pytania. Istnienie sieci VPN to nie to samo, co korzystanie z niej przez Twoją aplikację, a na urządzeniu z profilem służbowym lub drugim użytkownikiem oba mogą być prawdziwe naraz, gdy Twój proces rozmawia z internetem bezpośrednio. To wiersz aktywnej sieci rozstrzyga, co robią Twoje gniazda.
Zapytaj o sieć, której używa aplikacja
W kodzie test możliwości to cztery linie. Poproś o ACCESS_NETWORK_STATE w manifeście, a następnie porównaj możliwości aktywnej sieci z transportem 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);
Odpowiedź jest celowo wąska: czy domyślna sieć aplikacji jest teraz siecią VPN. Nie mówi nic o tym, który to VPN, czy należy do Ciebie ani czy tunel dociera na drugi koniec. true traktuj jako „ruch wychodzi jakimś tunelem” i prowadź własną ewidencję osobno. Wartość logiczna, która po cichu znaczy trzy rzeczy naraz, jest powodem, dla którego ekran ustawień zgłasza VPN jako wyłączony, gdy użytkownik przegląda przez niego strony.
Gdy przeciwnie, chcesz wiedzieć, czy istnieje jakakolwiek sieć VPN, iteruj po cm.getAllNetworks() i testuj każdy zestaw możliwości. Obie odpowiedzi rozjeżdżają się na urządzeniu z VPN per aplikacja, który obejmuje inne aplikacje, ale nie Twoją, i ta rozbieżność jest źródłem większości zgłoszeń błędów mówiących, że aplikacja nie widzi wyraźnie włączonego VPN.
Obserwuj zmiany zamiast odpytywać cyklicznie
Odpytywanie z timerem to zły kształt dla tego pytania, bo zmiany stanu przychodzą jako zdarzenia. Zarejestruj żądanie pasujące do transportu VPN i pozwól frameworkowi powiedzieć Ci, gdy pasująca sieć pojawi się, zniknie lub zmieni:
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(request, callback);
Sieć VPN nigdy nie niesie NET_CAPABILITY_NOT_VPN, więc jawne usunięcie dokumentuje intencję. Bez tego żądanie nadal pasuje, ale następna osoba czytająca kod musi się tego domyślić.
Rejestracja należy do zakresu, który potrzebuje obserwacji, zwykle ekranu lub usługi pierwszoplanowej. Wyrejestruj w onStop lub onDestroy i nie zostawiaj callbacku przypiętego do aktywności, która już zniknęła. Aplikacja bez widocznego komponentu jest złym gospodarzem dla stałego obserwatora, a zapomniane callbacki to te, które wywracają się po zmianie konfiguracji.
Trzymaj trzy pytania osobno
Większość zamieszania w tym obszarze bierze się z traktowania trzech różnych kontroli jako jednej. Używają różnych API i każda może być prawdziwa, gdy pozostałe są fałszywe.
| Question | How you answer it | What a yes means |
|---|---|---|
| Did the user grant VPN consent to my app? | VpnService.prepare() returns null | Consent exists; the tunnel may still be down |
| Is the device sending default traffic through a VPN? | TRANSPORT_VPN on the active network | Something is tunneling, not necessarily your app |
| Did this request arrive through the VPN? | Source address seen by the server | Proof for that connection, nothing wider |
Trzeci wiersz to ten, na który ludzie próbują odpowiedzieć po stronie klienta i tam odpowiedzieć się nie da. Daj klientom VPN własną pulę adresów i czytaj adres źródłowy na serwerze; to jedyny dowód, który przetrwa zmianę tunelu przez użytkownika w połowie sesji.
Kolejność weryfikacji, która wyklucza fałszywe trafienia
Starszy przykładowy kod wciąż w obiegu wywołuje getNetworkInfo(ConnectivityManager.TYPE_VPN). To wywołanie jest przestarzałe i w kilku wydaniach zwracało null nawet przy połączonym VPN, dlatego tak wiele skopiowanych fragmentów wygląda na zepsute na urządzeniu, które działa. Testy możliwości omijają tę pułapkę i zachowują się tak samo od Androida 5.0. Jeśli utrzymujesz bazę kodu, która nadal je wywołuje, zastąp wywołanie, zamiast opakowywać je w fallback, bo to właśnie fallback wytworzył błędną odpowiedź.
Jedno skrócenie zasługuje na ostrzeżenie. Porównanie publicznego adresu raportowanego przez zewnętrzną usługę z adresem wyjściowym VPN coś dowodzi o ścieżce, ale dodaje żądanie strony trzeciej do każdej kontroli, zależy od dostępności tej usługi i może zostać zbuforowane lub zablokowane. Trzymaj to jako diagnostykę ręczną, nigdy jako kontrolę stanu wewnątrz aplikacji.
Wykonaj kontrole w tej kolejności i zatrzymaj się na pierwszej, która nie zgadza się z relacją użytkownika: lista transportów z powłoki, możliwości aktywnej sieci, zdarzenia callback podczas włączania i wyłączania tunelu, a potem adres źródłowy po stronie serwera. Jeśli wszystkie cztery się zgadzają, a aplikacja nadal działa źle, błąd jest w Twojej własnej maszynie stanów, a nie w widzeniu sieci przez Androida.