No pots preguntar a Android si hi ha una VPN connectada. VpnService no exposa cap getter de connexió, i la icona del pany a la barra d'estat la dibuixa el sistema, no pas res que la teva aplicació pugui llegir. El que sí pots fer és fer a la pila de connectivitat una pregunta més estreta: quina xarxa fa servir el trànsit d'aquesta aplicació, i aquesta xarxa transporta el transport VPN. Aquest manual recorre les comprovacions en l'ordre que evita els falsos positius, des d'una ordre shell al dispositiu fins al callback que deixes funcionant a l'aplicació.

Comprova el dispositiu des d'una shell abans de tocar el codi de l'aplicació

Primer obtén una línia base. adb shell dumpsys connectivity imprimeix cada xarxa que el sistema coneix, i cada bloc llista els seus transports, així que una xarxa VPN apareix amb VPN en aquesta llista. Llegeix el bloc de la xarxa per defecte abans que res, després busca altres xarxes que transportin el mateix transport.

Dues sortides responen preguntes diferents. Que existeixi una xarxa VPN no és el mateix que la teva aplicació la faci servir, i en un dispositiu amb perfil de treball o un segon usuari totes dues coses poden ser certes alhora mentre el teu procés parla directament amb internet. La línia de la xarxa activa és la que decideix què fan els teus sockets.

Demana la xarxa que fa servir l'aplicació

Al codi, la prova de capacitats són quatre línies. Demana ACCESS_NETWORK_STATE al manifest i després compara les capacitats de la xarxa activa amb el 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);

La resposta és deliberadament estreta: si la xarxa per defecte de l'aplicació és una VPN ara mateix. No diu res sobre quina VPN és, si és teva o si el túnel arriba al seu extrem llunyà. Tracta un true com “el trànsit surt per algun túnel” i mantén la teva pròpia comptabilitat a part. Un booleà que en silenci significa tres coses alhora és com una pantalla de configuració acaba informant que la VPN està desactivada mentre l'usuari navega a través d'ella.

Quan el que necessites saber és si hi ha alguna xarxa VPN, itera cm.getAllNetworks() i prova cada conjunt de capacitats. Les dues respostes divergeixen en un dispositiu que executa una VPN per aplicació que cobreix altres aplicacions però no la teva, i aquesta divergència és l'origen de la majoria d'informes d'errors que diuen que l'aplicació no veu una VPN que clarament està activa.

Vigila els canvis en lloc d'enquestar

Enquestar amb un temporitzador és la forma incorrecta per a aquesta pregunta, perquè els canvis d'estat arriben com a esdeveniments. Registra una sol·licitud que coincideixi amb el transport VPN i deixa que el framework t'avisi quan aparegui, desaparegui o canviï una xarxa coincident:

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

Una xarxa VPN no transporta mai NET_CAPABILITY_NOT_VPN, així que l'eliminació explícita documenta la intenció. Sense això la sol·licitud encara coincideix, però la propera persona que llegeixi el codi haurà de deduir-ho.

El registre pertany a l'àmbit que necessita l'observació, normalment una pantalla o un servei en primer pla. Cancel·la el registre a onStop o onDestroy, i no deixis un callback enganxat a una activitat que ja no hi és. Una aplicació sense component visible és un mal amfitrió per a un observador permanent, i els callbacks que oblides són els que es pengen després d'un canvi de configuració.

Mantén tres preguntes separades

La major part de la confusió en aquesta àrea ve de tractar tres comprovacions diferents com una de sola. Fan servir API diferents, i cadascuna pot ser certa mentre les altres són falses.

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

La tercera fila és la que la gent intenta respondre al client, i allà no es pot respondre. Dona als clients VPN un conjunt d'adreces propi i llegeix l'adreça d'origen al servidor; és l'única prova que sobreviu quan un usuari canvia de túnel a mig sessió.

Ordre de verificació que descarta els falsos positius

Hi ha codi d'exemple antic encara en circulació que crida getNetworkInfo(ConnectivityManager.TYPE_VPN). Aquesta crida està obsoleta, i en diverses versions retornava null fins i tot amb una VPN connectada, i per això tants fragments copiats semblen trencats en un dispositiu que funciona. Les comprovacions de capacitats eviten el parany i es comporten igual des d'Android 5.0. Si mantens una base de codi que encara la crida, substitueix la crida en lloc d'embolcallar-la en un fallback, perquè el fallback és el que va produir la resposta incorrecta.

Una drecera mereix un avís. Comparar l'adreça pública que informa un servei extern amb l'adreça de sortida de la VPN sí que demostra alguna cosa sobre el camí, però afegeix una sol·licitud a tercers a cada comprovació, depèn que aquest servei segueixi actiu i pot ser emmagatzemat en caché o bloquejat. Mantén-ho com a diagnòstic manual, mai com a comprovació d'estat dins de l'aplicació.

Executa les comprovacions en aquest ordre i atura't a la primera que no coincideixi amb l'informe de l'usuari: llistat de transports a la shell, capacitats de la xarxa activa, esdeveniments de callback mentre alternes el túnel, i després l'adreça d'origen al servidor. Si les quatre coincideixen i l'aplicació encara es comporta malament, l'error és a la teva pròpia màquina d'estats i no en la visió que Android té de la xarxa.

Articles relacionats