這段程式碼在 Android 10 (API 29) 及以上的手機上仍然會失敗,並且會再次導致 error=13, Permission denied。
會出錯的程式碼:
把 .so 檔從 APK 複製到 App 的私有目錄 (/files/),然後設定可執行權限 (setExecutable)
這正是 Google 在 Android 10 之後嚴格禁止的行為(Violation):
- 安全性限制:Android 系統禁止應用程式在「可寫入」的目錄(如
files或cache)執行二進位檔案。 - 無效的操作:雖然 Java 的
outFile.setExecutable(true)可能回傳成功,但當你真正呼叫ProcessBuilder.start()時,Linux Kernel 會直接攔截並拒絕執行。
正確的修改方式
請完全捨棄複製檔案的邏輯。既然你已經在 build.gradle 設定了 useLegacyPackaging = true,系統已經幫你把檔案放在正確的位置了。
你只需要直接回傳系統路徑即可。
修改後的代碼如下:
Java
private File prepareOvpnExecutable(Context context) throws FileNotFoundException {
// 1. 獲取系統安裝 Native Library 的目錄
// 這個目錄是系統管理的,具有 "可執行" 但 "唯讀" 的權限,符合 Android 10+ 安全規範
String nativeLibDir = context.getApplicationInfo().nativeLibraryDir;
// 2. 拼接檔名
// 注意:檔案必須命名為 "libovpnexec.so",系統才會在安裝時放到這裡
File ovpnFile = new File(nativeLibDir, "libovpnexec.so");
// 3. 檢查是否存在 (用於除錯)
if (!ovpnFile.exists()) {
throw new FileNotFoundException("OpenVPN binary not found at: " + ovpnFile.getAbsolutePath() +
". Please ensure 'useLegacyPackaging=true' in build.gradle and file is named 'libovpnexec.so' in jniLibs.");
}
// 4. 不需要 setExecutable,系統目錄預設即為可執行
return ovpnFile;
}
為了讓上述程式碼運作,請務必確認兩件事:
- 檔名必須正確:在你的專案路徑 src/main/jniLibs/arm64-v8a/ 裡,檔案名稱必須是 libovpnexec.so。
- ❌
ovpnexec(不行) - ❌
ovpnexec.so(不行,必須有 lib 前綴) - ✅
libovpnexec.so(正確)
- ❌
- Gradle 設定必須正確 (如前次對話所述):在 app/build.gradle 中確保有:Groovy
android { packagingOptions { // 或 packaging jniLibs { useLegacyPackaging = true } } }
總結
不要手動複製檔案。讓 Android 系統幫你安裝,然後你直接去指那個安裝好的路徑。這是目前唯一能在 Android 10/11/12/13/14 穩定運作的方法。