Negalite paklausti Android, ar VPN prisijungtas. VpnService neteikia prisijungimo būsenos getter'io, o rakto piktogramą būsenos juostoje piešia sistema, o ne kažkas, ką jūsų programa gali nuskaityti. Ką galite padaryti, tai užduoti ryšio stekui siauresnį klausimą: kurį tinklą naudoja šios programos srautas ir ar tas tinklas perneša VPN transportą. Šis vadovas eina patikrinimus tokia tvarka, kuri išvengia klaidingų teigiamų rezultatų: nuo shell komandos įrenginyje iki callback, kurį paliekate veikti programoje.

Prieš liesdami programos kodą, patikrinkite įrenginį iš shell

Pirmiausia gaukite bazinę būseną. adb shell dumpsys connectivity atspausdina kiekvieną sistemai žinomą tinklą, o kiekvienas blokas išvardija savo transportus, todėl VPN tinklas tame sąraše pasirodo su VPN. Pirmiausia perskaitykite numatytojo tinklo bloką, tada ieškokite kitų tinklų, pernešančių tą patį transportą.

Du išvedimai atsako į skirtingus klausimus. Tai, ar VPN tinklas egzistuoja, nėra tas pats, kas tai, ar jūsų programa jį naudoja, o įrenginyje su darbo profiliu ar antru vartotoju abu gali būti teisingi vienu metu, kol jūsų procesas tiesiogiai kalba su internetu. Aktyvaus tinklo eilutė yra ta, kuri nusprendžia, ką daro jūsų lizdai.

Paklauskite, kurį tinklą naudoja programa

Kode galimybių testas yra keturios eilutės. Manifeste paprašykite ACCESS_NETWORK_STATE, tada palyginkite aktyvaus tinklo galimybes su VPN transportu:

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

Atsakymas sąmoningai siauras: ar programos numatytasis tinklas šiuo metu yra VPN. Jis nieko nesako apie tai, kuris VPN, ar jis jūsų, ar tunelis pasiekia kitą galą. true traktuokite kaip „srautas išeina per kokį nors tunelį“ ir savo apskaitą laikykite atskirai. Loginė reikšmė, tyliai reiškianti tris dalykus vienu metu, yra priežastis, kodėl nustatymų ekranas praneša, kad VPN išjungtas, kol vartotojas naršo per jį.

Kai priešingai, jums reikia žinoti, ar apskritai yra koks nors VPN tinklas, kartokite cm.getAllNetworks() ir tikrinkite kiekvieną galimybių rinkinį. Du atsakymai išsiskiria įrenginyje, kuriame veikia konkrečiai programai skirtas VPN, apimantis kitas programas, bet ne jūsų, ir tas išsiskyrimas yra daugumos klaidų pranešimų, teigiančių, kad programa nemato aiškiai įjungto VPN, šaltinis.

Stebėkite pokyčius, o ne apklausinėkite

Apklausinėjimas laikmačiu yra netinkama forma šiam klausimui, nes būsenos pokyčiai ateina kaip įvykiai. Registruokite užklausą, atitinkančią VPN transportą, ir leiskite sistemai pasakyti, kada atitinkantis tinklas atsiranda, išnyksta ar pasikeičia:

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

VPN tinklas niekada neturi NET_CAPABILITY_NOT_VPN, todėl aiškus pašalinimas dokumentuoja ketinimą. Be jo užklausa vis tiek atitinka, bet kitas žmogus, skaitantis kodą, turi tai išsiaiškinti pats.

Registracija priklauso sričiai, kuriai reikia stebėjimo, dažniausiai ekranui arba fono paslaugai. Išregistruokite onStop arba onDestroy, ir nepalikite callback, prikabinto prie jau išnykusios veiklos. Programa be matomų komponentų yra prastas nuolatinio stebėtojo šeimininkas, o pamiršti callback yra tie, kurie lūžta po konfigūracijos pakeitimo.

Laikykite tris klausimus atskirai

Dauguma painiavos šioje srityje kyla dėl trijų skirtingų patikrinimų traktavimo kaip vieno. Jie naudoja skirtingas API, ir kiekvienas gali būti teisingas, kol kiti klaidingi.

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

Trečioji eilutė yra ta, kurią žmonės bando atsakyti kliente, ir ten jos atsakyti negalima. Suteikite VPN klientams atskirą adresų telkinį ir skaitykite šaltinio adresą serveryje; tai vienintelis įrodymas, išliekantis vartotojui sesijos viduryje pakeitus tunelį.

Patikrinimų tvarka, kuri pašalina klaidingus teigiamus rezultatus

Senesnis pavyzdinis kodas, vis dar cirkuliuojantis, kviečia getNetworkInfo(ConnectivityManager.TYPE_VPN). Tas kvietimas nebenaudotinas, ir keliose versijose jis grąžino null net tada, kai VPN buvo prisijungtas, todėl tiek daug nukopijuotų fragmentų atrodo sulaužyti veikiančiame įrenginyje. Galimybių patikrinimai išvengia šios spąstų ir elgiasi taip pat nuo Android 5.0. Jei palaikote kodų bazę, kuri jį vis dar kviečia, pakeiskite kvietimą, o ne apvyniokite jį atsargine priemone, nes būtent atsarginė priemonė ir sukūrė klaidingą atsakymą.

Vienas trumpinys vertas įspėjimo. Viešo adreso, kurį praneša išorinė paslauga, palyginimas su VPN išėjimo adresu ką nors įrodo apie kelią, bet jis prideda trečiosios šalies užklausą prie kiekvieno patikrinimo, priklauso nuo to, ar ta paslauga veikia, ir gali būti talpykloje arba užblokuotas. Laikykite jį rankine diagnostika, niekada ne būsenos patikrinimu programos viduje.

Atlikite patikrinimus šia tvarka ir sustokite ties pirmuoju, kuris nesutampa su vartotojo pranešimu: transportų sąrašas iš shell, aktyvaus tinklo galimybės, callback įvykiai jungiant ir išjungiant tunelį, tada šaltinio adresas serverio pusėje. Jei visi keturi sutampa, o programa vis tiek veikia netinkamai, klaida yra jūsų pačių būsenos mašinoje, o ne Android tinklo matyme.

Susiję straipsniai