Не можете да попитате Android дали VPN е свързан. VpnService не предоставя метод за състоянието, а иконата с ключ в лентата за състояние се рисува от системата, а не от нещо, което приложението ви може да прочете. Това, което можете, е да зададете на стека за свързаност по-тесен въпрос: коя мрежа използва трафикът на това приложение и носи ли тази мрежа транспорта VPN. Това ръководство минава през проверките в реда, който избягва фалшивите положителни резултати, от команда shell на устройството до callback, който оставяте да работи в приложението.

Проверете устройството от shell, преди да пипате код на приложението

Първо вземете базова линия. adb shell dumpsys connectivity отпечатва всяка мрежа, която системата познава, и всеки блок изброява транспортите си, така че VPN мрежа се появява с VPN в този списък. Прочетете блока на мрежата по подразбиране преди всичко друго, после търсете други мрежи, които носят същия транспорт.

Два изхода отговарят на различни въпроси. Дали съществува VPN мрежа не е същото като дали приложението ви я използва, а на устройство с работен профил или втори потребител и двете могат да са верни едновременно, докато процесът ви говори с интернет директно. Редът на активната мрежа е този, който решава какво правят сокетите ви.

Питайте за мрежата, която приложението използва

В кода тестът за възможности е четири реда. Заявете ACCESS_NETWORK_STATE в манифеста, после сравнете възможностите на активната мрежа с транспорта 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);

Отговорът е умишлено тесен: дали мрежата по подразбиране на приложението е VPN в момента. Той не казва нищо за това кой VPN е, дали е ваш и дали тунелът стига до далечния си край. Третирайте true като „трафикът излиза през някакъв тунел“ и дръжте собствената си отчетност отделно. Булева стойност, която мълчаливо означава три неща наведнъж, е начинът, по който екран с настройки в крайна сметка отчита VPN като изключен, докато потребителят разглежда през него.

Когато вместо това трябва да знаете дали има каквато и да е VPN мрежа, итерирайте cm.getAllNetworks() и тествайте всеки набор от възможности. Двата отговора се разминават на устройство с VPN за отделни приложения, който покрива други приложения, но не вашето, и това разминаване е източникът на повечето доклади за грешки, които казват, че приложението не вижда VPN, който очевидно е включен.

Следете промените вместо да правите анкети

Анкетирането по таймер е грешната форма за този въпрос, защото промените на състоянието идват като събития. Регистрирайте заявка, която съответства на транспорта VPN, и оставете рамката да ви каже кога се появи, изчезне или промени съответстваща мрежа:

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

VPN мрежа никога не носи NET_CAPABILITY_NOT_VPN, така че явното премахване документира намерението. Без него заявката пак съответства, но следващият човек, който чете кода, трябва да го изведе сам.

Регистрацията принадлежи на обхвата, който се нуждае от наблюдението, обикновено екран или услуга на преден план. Отменете регистрацията в onStop или onDestroy и не оставяйте callback, прикачен към активност, която вече я няма. Приложение без видим компонент е лош домакин за постоянен наблюдател, а callbacks, които сте забравили, са тези, които се сриват след промяна на конфигурацията.

Дръжте три въпроса разделени

Повечето объркване в тази област идва от това, че три различни проверки се третират като една. Те използват различни API и всяка може да е вярна, докато другите са неверни.

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

Третият ред е този, който хората се опитват да отговорят на клиента, а там не може да се отговори. Дайте на VPN клиентите собствен пул от адреси и четете адреса на източника на сървъра; това е единственото доказателство, което оцелява, когато потребител смени тунела по средата на сесията.

Ред на проверка, който изключва фалшиви положителни резултати

По-стар примерен код, който все още се върти, извиква getNetworkInfo(ConnectivityManager.TYPE_VPN). Това извикване е отхвърлено и в няколко издания връщаше null дори при свързан VPN, затова толкова много копирани фрагменти изглеждат счупени на устройство, което работи. Проверките за възможности избягват капана и се държат еднакво от Android 5.0 нататък. Ако поддържате кодова база, която все още го извиква, заменете извикването, вместо да го обвивате в резервен вариант, защото именно резервният вариант произведе грешния отговор.

Един пряк път заслужава предупреждение. Сравняването на публичния адрес, който външна услуга съобщава, с адреса на изхода на VPN наистина доказва нещо за пътя, но добавя заявка към трета страна към всяка проверка, зависи от това услугата да остане активна и може да бъде кеширано или блокирано. Дръжте го като ръчна диагностика, никога като проверка на състоянието в приложението.

Изпълнете проверките в този ред и спрете на първата, която не съвпада с доклада на потребителя: списък на транспортите в shell, възможности на активната мрежа, събития callback, докато превключвате тунела, после адрес на източника от страна на сървъра. Ако и четирите съвпадат, а приложението продължава да се държи зле, грешката е във вашата собствена машина на състоянията, а не в представата на Android за мрежата.

Подобни статии