Помошниот бинарен файл на OpenVPN не работи на Android 10: пристапот е одбиен
Android 10 одбива да изврши помошен файл што апликацијата го запишала во сопственото складиште. Прочитајте logcat, доставете го помошникот како нативна библиотека и поправете ги bundle-изградбите.
Апликацијата работеше на Android 9 и умира на истиот телефон по ажурирањето на Android 10. Вашиот профил е недопрен, сертификатите се исти, а падот доаѓа пред каков било сообраќај за автентикација. Logcat прво покажува нулти показател, а неколку реда погоре стои Cannot run program ...: error=13, Permission denied. Таа порака е за извршување на файл, не за читање на вашата конфигурација.
Што всушност одбива грешката 13
Android 10, ниво на API 29, ги спречи апликациите да извршуваат файлови што ги запишале во сопствениот директориум со податоци. Правилото цели кон складиште што е прво запишливо, а потоа извршливо, и токму тоа го создава стартерот што копира од assets. Постари изградби на јадрото ics-openvpn распакуваат извршен файл openvpn од APK-то при првото стартување и го покренуваат со ProcessBuilder. Распакувањето сѐ уште успева; одбиено е токму извршувањето. Апликација што цели кон API 29 го наследува правилото дури и кога работи на уред што излегол со постара верзија, затоа едно ажурирање може да скрши изградба што никој не ја допрел.
Ништо во одбивањето не е специфично за едно име на папка. Файл што апликацијата го создала под својот директориум со податоци се третира како неизвршлив без оглед на патеката, па затоа getFilesDir(), getCacheDir() и прилагоден поддиректориум сите не успеваат на ист начин.
Одделете го падот на помошникот од проблем со профилот
Читајте го дневникот околу стартувањето на процесот наместо да скролувате од врвот. Потписот што го барате е редот error=13, а трагата од стекот погоре обично го именува методот на стартерот што го повикал ProcessBuilder:
adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"Ако помошникот никогаш не стартувал, воопшто нема да видите излез од дневникот на OpenVPN: ни преговарање за шифри, ни испратени рути, ни DNS-конфигурација. Ако тие редови се појават, а неуспехот дојде подоцна, бинарниот файл работел и проблемот е во профилот, серверот или слојот за рутирање. Таа единствена разлика заштедува многу време што инаку би отишло на уредување на файлови .ovpn што никогаш не биле виновни. Таа ви кажува и која од двете поправки подолу ви е навистина потребна.
Проверете го и нивото на API на уредот што го тестирате, а не тоа во вашата конфигурација за изградба. Емулаторска слика на API 28 не репродуцира ништо, бидејќи ограничувањето касае само од 29 нагоре. Истовремено проверете го целниот SDK во вашиот файл за изградба: токму неговото подигање го воведува ова правило.
adb shell getprop ro.build.version.sdkЗошто копирањето на бинарниот файл на друго место не помага
Очигледниот заобиколен пат, запишување на помошникот во друг запишлив директориум, не успева од истата причина како оригиналот. Android сѐ уште дозволува извршување на споделени библиотеки инсталирани во директориумот со нативни библиотеки на апликацијата, бидејќи ги пакува инсталаторот, а не се запишуваат за време на извршувањето. Токму тој директориум е единственото место кон кое стартерот треба да покажува. Сѐ што самите распакувате за време на извршувањето, како и да го наречете, останува во одбиената категорија.
Надворешното складиште е полошо: тоа е монтирано со noexec, а помошник поставен таму не може да го стартува ниту една апликација. Бришењето на податоците на апликацијата исто така не го враќа старото однесување, бидејќи следното стартување едноставно ја создава истата неизвршлива копија.
Насочете го стартерот кон спакувана библиотека
Преименувајте го помошникот во име на споделена библиотека и доставете го под jniLibs за секој ABI што го поддржувате, па нека стартерот од API 28 натаму ја враќа патеката до инсталираната библиотека:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return new File(context.getApplicationInfo().nativeLibraryDir,
"libovpnexec.so").getPath();
}Библиотеката мора да се распакува при инсталацијата за таа патека да содржи вистински файл, затоа поставете android:extractNativeLibs="true" на елементот <application>. Ако спојувачот на манифестот внесе друга вредност, додајте tools:replace="android:extractNativeLibs" до него. референцата за елементот application документира што контролира атрибутот.
Внимавајте на дуплика локална променлива кога рачно го крпите стартерот. Постарите извори подоцна во истиот метод декларираат nativeAPI, а второто декларирање е грешка при компајлирање, не при извршување, затоа изградбата брзо не успева и грешката лесно се забележува. Одржувајте ја листата со ABI во Gradle-блокот усогласена со папките што ги додавате.
Усогласете го артефактот за издание со тестната изградба
Debug APK може да го подигне тунелот додека release bundle сѐ уште паѓа, бидејќи bundle ги пакува нативните библиотеки во друга форма и може да ги служи компресирани од архивата. Ако не успеваат само изградбите добиени од bundle, додајте го ова во gradle.properties и изградете повторно:
android.bundle.enableUncompressedNativeLibs=falseПотоа потврдете дека инсталираниот пакет го пријавува ABI-то што го очекувате, и повторете ја проверката на 32-битен и 64-битен уред ако ги доставувате и двата. Менаџерот на пакети го именува избраниот ABI, па уредот што се вратил на 32 бита се појавува овде, а не во извештај за пад:
adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbiБиблиотека што недостасува за едно ABI ја репродуцира оригиналната грешка за пристап само на тој уред, затоа еден успешен тест-телефон не е доказ. Доставете го знамето од манифестот, името на библиотеката и поставката за bundle заедно; менување на едното и заборавање на другите ве враќа на истиот пад. Трајното решение е премин кон одржувано издание на јадрото, наместо засекогаш да ја носите закрпата.