Android 系統禁止應用程式在「可寫入」的目錄(如 files 或 cache)執行二進位檔案

這段程式碼在 Android 10 (API 29) 及以上的手機上仍然會失敗,並且會再次導致 error=13, Permission denied

會出錯的程式碼:

.so 檔從 APK 複製到 App 的私有目錄 (/files/),然後設定可執行權限 (setExecutable)

這正是 Google 在 Android 10 之後嚴格禁止的行為(Violation):

  1. 安全性限制:Android 系統禁止應用程式在「可寫入」的目錄(如 filescache)執行二進位檔案。
  2. 無效的操作:雖然 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;
}

為了讓上述程式碼運作,請務必確認兩件事:

  1. 檔名必須正確:在你的專案路徑 src/main/jniLibs/arm64-v8a/ 裡,檔案名稱必須是 libovpnexec.so。
    • ovpnexec (不行)
    • ovpnexec.so (不行,必須有 lib 前綴)
    • libovpnexec.so (正確)
  2. Gradle 設定必須正確 (如前次對話所述):在 app/build.gradle 中確保有:Groovyandroid { packagingOptions { // 或 packaging jniLibs { useLegacyPackaging = true } } }

總結

不要手動複製檔案。讓 Android 系統幫你安裝,然後你直接去指那個安裝好的路徑。這是目前唯一能在 Android 10/11/12/13/14 穩定運作的方法。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *