Non podes preguntarlle a Android se hai unha VPN conectada. VpnService non expón ningún getter de conexión, e a icona da chave na barra de estado debúxaa o sistema, non algo que a túa app poida ler. O que si podes facer é facerlle á pila de conectividade unha pregunta máis estreita: que rede usa o tráfico desta app e se esa rede leva o transporte VPN. Este manual percorre as comprobacións na orde que evita os falsos positivos, desde un comando shell no dispositivo ata o callback que deixas executándose na app.

Comproba o dispositivo desde un shell antes de tocar o código da app

Primeiro obtén unha liña base. adb shell dumpsys connectivity imprime todas as redes que coñece o sistema, e cada bloque enumera os seus transportes, así que unha rede VPN aparece con VPN nesa lista. Le o bloque da rede predeterminada antes ca nada e despois busca outras redes que leven o mesmo transporte.

Dúas saídas responden a preguntas distintas. Que exista unha rede VPN non é o mesmo que a túa app a use, e nun dispositivo con perfil de traballo ou un segundo usuario ambas as cousas poden ser certas á vez mentres o teu proceso fala directamente con internet. A liña da rede activa é a que decide que fan os teus sockets.

Pide a rede que usa a app

No código, a proba de capacidades son catro liñas. Solicita ACCESS_NETWORK_STATE no manifest e despois compara as capacidades da rede activa co transporte 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);

A resposta é deliberadamente estreita: se a rede predeterminada da app é unha VPN agora mesmo. Non di nada sobre que VPN é, se é túa ou se o túnel chega ao seu extremo remoto. Trata un true como «o tráfico sae por algún túnel» e mantén a túa propia contabilidade á parte. Un booleano que en silencio significa tres cousas á vez é a razón pola que unha pantalla de axustes acaba informando de que a VPN está desactivada mentres o usuario navega a través dela.

Cando en cambio necesitas saber se hai algunha rede VPN, itera cm.getAllNetworks() e proba cada conxunto de capacidades. As dúas respostas diverxen nun dispositivo que executa unha VPN por app que cobre outras aplicacións pero non a túa, e esa diverxencia é a orixe da maioría dos informes de erros que din que a app non ve unha VPN que claramente está activa.

Vixía os cambios en lugar de sondar

Sondar cun temporizador é a forma incorrecta para esta pregunta, porque os cambios de estado chegan como eventos. Rexistra unha solicitude que coincida co transporte VPN e deixa que o framework che avise cando apareza, desapareza ou cambie unha rede coincidente:

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

Unha rede VPN nunca leva NET_CAPABILITY_NOT_VPN, así que a eliminación explícita documenta a intención. Sen ela a solicitude segue coincidindo, pero a seguinte persoa que lea o código ten que deducilo.

O rexistro pertence ao ámbito que necesita a observación, normalmente unha pantalla ou un servizo en primeiro plano. Cancela o rexistro en onStop ou onDestroy, e non deixes un callback enganchado a unha actividade que xa non existe. Unha app sen compoñente visible é un mal anfitrión para un observador permanente, e os callbacks que esqueces son os que caen tras un cambio de configuración.

Mantén tres preguntas separadas

A maior parte da confusión nesta área vén de tratar tres comprobacións distintas como unha soa. Usan API distintas, e cada unha pode ser certa mentres as outras son falsas.

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

A terceira fila é a que a xente tenta responder no cliente, e alí non se pode responder. Dálles aos clientes VPN o seu propio conxunto de enderezos e le o enderezo de orixe no servidor; é a única proba que sobrevive cando un usuario cambia de túnel a metade de sesión.

Orde de verificación que descarta os falsos positivos

Aínda circula código de exemplo antigo que chama getNetworkInfo(ConnectivityManager.TYPE_VPN). Esa chamada está obsoleta, e en varias versións devolvía null mesmo cunha VPN conectada, por iso tantos fragmentos copiados parecen rotos nun dispositivo que funciona. As comprobacións de capacidades evitan a trampa e compórtanse igual desde Android 5.0. Se mantés unha base de código que aínda a chama, substitúe a chamada en lugar de envolvela nun fallback, porque o fallback foi o que produciu a resposta incorrecta.

Un atallo merece un aviso. Comparar o enderezo público que informa un servizo externo co enderezo de saída da VPN si demostra algo sobre a ruta, pero engade unha solicitude a terceiros a cada comprobación, depende de que ese servizo siga activo e pode ser almacenado en caché ou bloqueado. Manteno como diagnóstico manual, nunca como a comprobación de estado dentro da app.

Executa as comprobacións nesta orde e detente na primeira que non coincida co informe do usuario: listaxe de transportes no shell, capacidades da rede activa, eventos de callback mentres alternas o túnel e despois o enderezo de orixe no lado do servidor. Se as catro coinciden e a app segue comportándose mal, o erro está na túa propia máquina de estados e non na visión que Android ten da rede.

Artigos relacionados