遇到的錯誤是因為 OpenVPN 的啟動機制與 useLegacyPackaging = false 互相衝突。
核心原因分析
- useLegacyPackaging = false 的作用:這個設定告訴 Android 建置系統(AGP),不要把 APK 裡面的 .so 檔案解壓縮到手機的 /data/app/…/lib/ 目錄下。它會讓系統直接從 APK 包裡面讀取這些函式庫(為了節省手機空間)。這就是為什麼你用 adb ls 看到目錄是空的。
- OpenVPN (ics-openvpn) 的運作方式:Cake-VPN(以及大多數基於 ics-openvpn 的專案)並不是像一般 App 那樣只用 System.loadLibrary。它實際上是把 libovpnexec.so 當作一個可執行檔 (Executable) 來跑。Java 程式碼會嘗試去執行類似這樣的指令:/data/app/…/lib/arm64/libovpnexec.so …。因為你設了 false,檔案不存在於那個路徑上,導致 java.io.IOException,程式崩潰。
解決方案
為了同時修復崩潰問題並通過 Google Play 的 16 KB Page Size 檢查,你需要採取兩步走的策略:
第一步:修復崩潰(改回 True)
你必須允許 Native Library 被解壓縮,因為這個 App 的架構依賴於實體檔案的存在。
修改 build.gradle:
Groovy
android {
packaging {
jniLibs {
// 改回 true (或者直接刪除這段,因為預設通常是 true)
// 這樣 Android 才會把 .so 檔解壓到 /data/app/.../lib 目錄
useLegacyPackaging = true
}
}
// ... 其他設定
}
做完這步,你的 App 就能正常啟動了,但可能還過不了 16 KB 檢查。接下來看第二步。
第二步:真正解決 16 KB 對齊問題 (Rebuild NDK)
既然我們必須解壓縮 .so 檔(useLegacyPackaging = true),那麼 Google Play 的要求就是:這些解壓縮出來的 ELF (.so) 檔案,其 Segment 必須是 16 KB 對齊的。
這代表你不能只改 build.gradle 的 packaging 設定,你需要重新編譯 C/C++ 原始碼,並加上 Linker flags。
請在你的 build.gradle (Module level) 加入以下設定,強制 NDK 使用 16KB 對齊:
android {
defaultConfig {
// ... 其他設定
externalNativeBuild {
cmake {
// 這是關鍵:告訴 Linker 使用 16KB (16384 bytes) 的 Page Size
cppFlags "-Wl,-z,max-page-size=16384"
arguments "-DANDROID_STL=c++_shared"
}
// 如果你是用 ndk-build (Android.mk),則需要用不同的參數,但大多數現在都用 cmake
}
ndk {
abiFilters "arm64-v8a", "x86_64"
}
}
}
注意:
如果你的專案不是透過 CMake 編譯(例如直接引用 libs/ 資料夾下的預編譯 .so 檔),那麼你無法透過 Gradle 修改對齊。你必須找到這些 .so 檔的原始碼重新編譯,或者尋找已經支援 16KB Alignment 的第三方庫版本。
驗證方法
修改並重新 Build 出 APK 後,請使用 Android SDK 提供的工具 zipinfo 或 Linux 的 readelf 來檢查。
- 解壓 APK,取出裡面的
.so檔。 - 在終端機執行:
readelf -l libovpnexec.so | grep LOAD - 檢查輸出中的
Align欄位。如果是0x4000(16384),代表成功;如果是0x1000(4096),代表失敗。
總結
- 把
useLegacyPackaging改回true(讓 OpenVPN 找得到檔案)。 - 在
externalNativeBuild加入-Wl,-z,max-page-size=16384(讓編譯出來的檔案符合 Google 規範)。