Du kan inte fråga Android om en VPN är ansluten. VpnService exponerar ingen getter för anslutningstillstånd, och nyckelikonen i statusfältet ritas av systemet, inte av något din app kan läsa. Det du kan göra är att ställa anslutningsstacken en snävare fråga: vilket nätverk använder den här appens trafik, och bär det nätverket VPN-transporten. Den här guiden går igenom kontrollerna i den ordning som undviker falska positiva svar, från ett shell-kommando på enheten till callbacken du låter köra i appen.

Kontrollera enheten från ett shell innan du rör appkoden

Skaffa först en baslinje. adb shell dumpsys connectivity skriver ut varje nätverk systemet känner till, och varje block listar sina transporter, så ett VPN-nätverk visas med VPN i den listan. Läs blocket för standardnätverket före allt annat och leta sedan efter andra nätverk som bär samma transport.

Två utdata svarar på olika frågor. Att ett VPN-nätverk finns är inte samma sak som att din app använder det, och på en enhet med arbetsprofil eller en andra användare kan båda vara sanna samtidigt medan din process pratar direkt med internet. Raden för det aktiva nätverket är den som avgör vad dina sockets gör.

Fråga efter nätverket appen använder

I kod är funktionstestet fyra rader. Begär ACCESS_NETWORK_STATE i manifestet och jämför sedan det aktiva nätverkets funktioner med VPN-transporten:

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);

Svaret är medvetet snävt: är appens standardnätverk en VPN just nu. Det säger ingenting om vilken VPN, om den är din, eller om tunneln når den andra änden. Behandla en true som ”trafiken går ut genom någon tunnel” och håll din egen bokföring separat. En boolesk som tyst betyder tre saker samtidigt är anledningen till att en inställningsskärm rapporterar VPN som av medan användaren surfar genom den.

När du i stället behöver veta om något VPN-nätverk finns, iterera över cm.getAllNetworks() och testa varje funktionsuppsättning. De två svaren går isär på en enhet som kör en appspecifik VPN som täcker andra appar men inte din, och den isärgången är källan till de flesta felrapporter som säger att appen inte ser en VPN som tydligt är på.

Följ ändringar i stället för att polla

Polling med timer är fel form för den här frågan, eftersom tillståndsändringar kommer som händelser. Registrera en begäran som matchar VPN-transporten och låt ramverket berätta när ett matchande nätverk dyker upp, försvinner eller ändras:

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

Ett VPN-nätverk bär aldrig NET_CAPABILITY_NOT_VPN, så den uttryckliga borttagningen dokumenterar avsikten. Utan den matchar begäran fortfarande, men nästa person som läser koden måste lista ut det själv.

Registreringen hör till det omfång som behöver observationen, oftast en skärm eller en förgrundstjänst. Avregistrera i onStop eller onDestroy, och lämna inte en callback hängande på en aktivitet som redan är borta. En app utan synliga komponenter är en dålig värd för en permanent observatör, och de callbacks du glömmer är de som kraschar efter en konfigurationsändring.

Håll tre frågor åtskilda

Merparten av förvirringen på det här området kommer av att behandla tre olika kontroller som en. De använder olika API:er, och var och en kan vara sann medan de andra är falska.

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

Den tredje raden är den folk försöker svara på i klienten, och den kan inte besvaras där. Ge VPN-klienter sin egen adresspool och läs källadressen på servern; det är det enda beviset som överlever att en användare byter tunnel mitt i en session.

Verifieringsordning som utesluter falska positiva svar

Äldre exempelkod som fortfarande cirkulerar anropar getNetworkInfo(ConnectivityManager.TYPE_VPN). Det anropet är föråldrat, och på flera versioner returnerade det null även med en VPN ansluten, vilket är varför så många kopierade snuttar ser trasiga ut på en enhet som fungerar. Funktionskontroller undviker fällan och beter sig likadant från Android 5.0 och framåt. Om du underhåller en kodbas som fortfarande anropar det, ersätt anropet i stället för att linda in det i en reservväg, för det var reservvägen som gav det felaktiga svaret från början.

En genväg förtjänar en varning. Att jämföra den publika adress som en extern tjänst rapporterar med VPN:ens utgångsadress bevisar något om vägen, men det lägger till en tredjepartsbegäran i varje kontroll, beror på att tjänsten hålls igång och kan cachas eller blockeras. Håll det som en manuell diagnos, aldrig som tillståndskontrollen inuti appen.

Kör kontrollerna i den här ordningen och stanna vid den första som inte stämmer med användarens rapport: transportlista från shell, funktionerna hos aktivt nätverk, callback-händelser medan du slår av och på tunneln, och sedan källadressen på serversidan. Om alla fyra stämmer och appen fortfarande beter sig fel sitter felet i din egen tillståndsmaskin och inte i Androids bild av nätverket.

Relaterade artiklar