使用新版 API

本頁面將說明,您的應用程式如何在新的 OS 功能上執行時,使用新的 OS 功能 作業系統版本,同時保留與舊版裝置的相容性。

根據預設,應用程式中的 NDK API 參照是強力參照。 Android 的動態載入器會在您的程式庫 就會引發這個事件。如果找不到這些符號,應用程式會取消。這與 Java 的行為,在缺少的 API 才會擲回例外狀況 物件。

因此,NDK 會禁止您建立嚴格參照 比應用程式 minSdkVersion 更新的 API。這可以保護您不受 在測試期間,不慎誤發運送代碼,但無法載入 (UnsatisfiedLinkError 將從 System.loadLibrary() 擲回) 裝置。另一方面,因為要編寫使用 API 的程式碼比較困難 比應用程式的 minSdkVersion 來得新,因此您必須使用 dlopen()dlsym(),而不是一般函式呼叫。

使用強式參照的替代方案是使用弱參照。有點弱 載入程式庫時不存在的參照,結果位址為 並設為 nullptr 而不會載入。他們靜止不動 無法安全呼叫,但前提是呼叫站必須受到保護 即使 API 無法使用,也能執行其餘程式碼 正常呼叫 API,無需使用 dlopen()dlsym()

弱的 API 參照不需要動態連結器提供的額外支援, 因此適用於所有 Android 版本

在建構中啟用弱 API 參照

CMake

執行 CMake 時傳遞 -DANDROID_WEAK_API_DEFS=ON。如果透過以下應用程式使用 CMake: externalNativeBuild,請將以下內容新增到您的 build.gradle.kts (或 如果您仍在使用 build.gradle,則相當於:

android {
    // Other config...

    defaultConfig {
        // Other config...

        externalNativeBuild {
            cmake {
                arguments.add("-DANDROID_WEAK_API_DEFS=ON")
                // Other config...
            }
        }
    }
}

ndk-build

請將以下內容新增到 Application.mk 檔案中:

APP_WEAK_API_DEFS := true

如果還沒有 Application.mk 檔案,請在同一個中建立檔案 做為 Android.mk 檔案。您對 ndk-build 不需要 build.gradle.kts (或 build.gradle) 檔案。

其他建構系統

如果您使用的不是 CMake 或 ndk-build,請參閱建構作業的說明文件 系統,看看是否有建議啟用這項功能的方法。如果您的版本 系統不支援此選項,而您可以透過下列方式啟用此功能: 並在編譯時傳送下列旗標:

-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability

第一種做法是將 NDK 標頭設為允許弱式參照。第二個回合 不安全的 API 呼叫警告中。

詳情請參閱建構系統維護人員指南

防護的 API 呼叫

這項功能不會神奇地呼叫新的 API。事實上 是否會延遲載入時間錯誤,導致呼叫時間錯誤。這麼做的好處是 可在執行階段期間保持呼叫,並優雅改回使用,無論使用的是 替代實作方式,或通知使用者應用程式功能 或完全避開該程式碼路徑。

當您取消警戒時,Clang 可能會發出警告 (unguarded-availability) 呼叫 API,但應用程式的 minSdkVersion 無法使用。如果您是 使用 ndk-build 或我們的 CMake 工具鍊檔案,系統就會自動發出警告 啟用並升級為錯誤。

以下範例中的程式碼在沒有條件的情況下使用 API 這項功能已啟用,使用了 dlopen()dlsym()

void LogImageDecoderResult(int result) {
    void* lib = dlopen("libjnigraphics.so", RTLD_LOCAL);
    CHECK_NE(lib, nullptr) << "Failed to open libjnigraphics.so: " << dlerror();
    auto func = reinterpret_cast<decltype(&AImageDecoder_resultToString)>(
        dlsym(lib, "AImageDecoder_resultToString")
    );
    if (func == nullptr) {
        LOG(INFO) << "cannot stringify result: " << result;
    } else {
        LOG(INFO) << func(result);
    }
}

您可能會好奇,而函式名稱也會重複出現 ( 如果您編寫 C,簽章) 即可成功建構,但一律會 如果不小心拼寫了傳遞的函式名稱,則在執行階段執行備用選項 dlsym,而每個 API 都必須使用這個模式。

如果使用脆弱的 API 參照,上述函式可改寫為:

void LogImageDecoderResult(int result) {
    if (__builtin_available(android 31, *)) {
        LOG(INFO) << AImageDecoder_resultToString(result);
    } else {
        LOG(INFO) << "cannot stringify result: " << result;
    }
}

實際上,__builtin_available(android 31, *) 通電話 android_get_device_api_level(),快取結果,並將結果與 31 比較 (導入 AImageDecoder_resultToString() 的 API 級別)。

要決定要使用哪個 __builtin_available 值,最簡單的方法是: 試圖在沒有警衛的情況下進行建構 __builtin_available(android 1, *)),然後執行錯誤訊息中的指示。 例如對 AImageDecoder_createFromAAsset() 發出的未防護呼叫 minSdkVersion 24 會產生:

error: 'AImageDecoder_createFromAAsset' is only available on Android 30 or newer [-Werror,-Wunguarded-availability]

在這種情況下,呼叫應受 __builtin_available(android 30, *) 保護。 如果沒有發生建構錯誤, minSdkVersion,不需要防護,或是您的建構作業設定錯誤和 有unguarded-availability警告已停用。

或者,NDK API 參考資料也會在 「在 API 30 中導入」。如果沒有這類文字, 所有支援的 API 級別都能使用該 API。

避免 API 防護重複

如果您使用這個方法,應用程式可能會包含一些程式碼區段 也只能在足夠新裝置使用與其重複 __builtin_available() 會檢查每個函式,您就可以加上註解 例如要求特定 API 級別例如 ImageDecoder API 也就是在 API 30 中新增的,因此需要大量使用這些 API 的函式 可執行的工作如下:

#define REQUIRES_API(x) __attribute__((__availability__(android,introduced=x)))
#define API_AT_LEAST(x) __builtin_available(android x, *)

void DecodeImageWithImageDecoder() REQUIRES_API(30) {
    // Call any APIs that were introduced in API 30 or newer without guards.
}

void DecodeImageFallback() {
    // Pay the overhead to call the Java APIs via JNI, or use third-party image
    // decoding libraries.
}

void DecodeImage() {
    if (API_AT_LEAST(30)) {
        DecodeImageWithImageDecoder();
    } else {
        DecodeImageFallback();
    }
}

API 守衛的知識

Clang 對 __builtin_available 的使用方式非常特別。只有常值 (但可能已取代巨集) if (__builtin_available(...)) 可正常運作。平均 「if (!__builtin_available(...))」等一般作業無法運作 (Clang 會發出 unsupported-availability-guard 警告 unguarded-availability)。這可能會在未來的 Clang 版本中改善。詳情請見 LLVM 問題 33161

檢查 unguarded-availability 僅適用於其所在的函式範圍 使用方式。即使含有 API 呼叫的函式 只會從防護範圍內呼叫為了避免在 請參閱避免重複 API 防護

為什麼這不是預設值?

除非正確使用,否則高強度 API 參照與弱 API 之間的差異 參照就是前者會快速並顯而易見 後者要等到使用者採取導致缺少 API 的操作時才會失敗 。在此情況下,錯誤訊息並不明確 編譯時間「AFoo_bar() is not available」錯誤,就會造成獨立錯誤。取代為 強而有力的參考資料、錯誤訊息更清楚易懂 更安全的預設環境

由於這是新功能,因此幾乎不需編寫太多現有程式碼來處理 安全的行為非想在 Android 編寫的第三方程式碼 可能會出現這個問題,所以我們目前 預設行為模式

我們是建議您採用這項工具,但這麼做會增加問題 難以偵測及偵錯,您應該明白接受這些風險,而非 對您有所幫助

注意事項

這項功能適用於大多數 API,但在少數情況下不適用 這些研究有助於我們找出 能引導後續作業的標準

最不可能發生問題的是較新的 libc API。不同於 Android API 的標頭中會以 #if __ANDROID_API__ >= X 保護。 而不只是 __INTRODUCED_IN(X),這也能防止弱式宣告 確實可能影響使用者看見的成本由於最舊的 API 級別現代 NDK 支援 r21, 我們已推出常用的 libc API。已新增各個 libc API 版本 (請參閱 status.md),但更新的值越新,就越有可能 很少需要開發人員花費太多心力不過,如果您是 這些開發人員,目前您需要繼續使用 dlsym() 呼叫這些 minSdkVersion API。這是一個可以解決的問題 但這也可能導致所有應用程式發生原始碼相容性中斷的風險 ( 系統無法編譯包含 libc API 的 polyfill,這是因為 libc 和本機宣告中的 availability 屬性不符,因此, 我們無法確定何時或何時會修復這個問題

越來越多開發人員可能遇到的就是,如果程式庫 內含的新 API 比 minSdkVersion 更新。這項功能 啟用弱符號參照;但性不是弱點 參照。舉例來說,如果 minSdkVersion 是 24,您可以連結 libvulkan.so並向 vkBindBufferMemory2 發出保全通話,因為 自 API 24 以後的裝置可以使用 libvulkan.so。另一方面 如果你的minSdkVersion是 23 歲,則必須改回使用 dlopendlsym 因為在僅支援 API 23。我們不清楚修正這個案件的好方法,但長期下來 因為我們 (可能的話) 不再允許新的 建立新的程式庫

圖書館作者

如果您正在開發可在 Android 應用程式中使用的程式庫, 避免在公開標頭中使用這項功能這類 SDK 可安全地 過時程式碼,但若您使用 __builtin_available 像是內嵌函式或範本定義等標頭 即可啟用這項功能基於一樣的原因 功能,則應避免代您做出選擇 目標客戶。

如果您必須在公開標頭中執行這項操作,請務必 讓使用者知道需要啟用這項功能,並且 瞭解這樣做的風險