Daha yeni API'leri kullanma

Bu sayfada, uygulamanızın yeni bir işletim sistemi sisteminde çalışırken yeni işletim sistemi Eski cihazlarla uyumluluğu korurken işletim sistemi sürümleri.

Varsayılan olarak, uygulamanızdaki NDK API'lerine yapılan referanslar güçlü referanslardır. Kitaplığınız açıkken Android'in dinamik yükleyicisi bu sorunları gönül rahatlığıyla çözecektir yüklendi. Semboller bulunmazsa uygulama iptal eder. Bu, devre dışı bırakılır (bu şekilde, eksik API devre dışı bırakılana kadar çağrıldı.

Bu nedenle, NDK, Uygulamanızın minSdkVersion ürününden daha yeni API'ler. Bu, sizi testiniz sırasında çalışan ancak yüklenmeyen kodu yanlışlıkla göndermek (UnsatisfiedLinkError, System.loadLibrary() konumundan atılacak) cihazlar. Diğer yandan, API'ler kullanan kod yazmak daha zordur. uygulamanızın minSdkVersion değerinden daha yenidir çünkü API'leri dlopen() ve dlsym() tuşlarına basın.

Güçlü referanslar kullanmanın alternatifi, zayıf referanslar kullanmaktır. Zayıf kitaplığın yüklenmesi şu adresle sonuçlandığında bulunamayan referans: bu simgenin yüklenmemesi yerine nullptr olarak ayarlanması gerekir. Hala güvenli bir şekilde çağrılamaz, ancak çağrı siteleri aramayı önlemek için korunduğu sürece API'nin kullanılamadığı durumlarda, kodunuzun geri kalanı çalıştırılabilir ve dlopen() ve dlsym() uygulamalarına gerek kalmadan API'yi normal şekilde çağırın.

Zayıf API referansları, dinamik bağlayıcıdan ek destek gerektirmez. Android'in tüm sürümlerinde kullanılabilir.

Derlemenizde zayıf API referanslarını etkinleştirme

CMake

CMake çalıştırırken -DANDROID_WEAK_API_DEFS=ON öğesini geçirin. CMake kullanıyorsanız externalNativeBuild, aşağıdakini build.gradle.kts (veya Hâlâ build.gradle kullanıyorsanız harika bir eşdeğeri):

android {
    // Other config...

    defaultConfig {
        // Other config...

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

ndk-kurum

Application.mk dosyanıza aşağıdakileri ekleyin:

APP_WEAK_API_DEFS := true

Henüz Application.mk dosyanız yoksa dosyayı aynı Android.mk dosyanız olarak kullanılır. build.gradle.kts (veya build.gradle) dosyası ndk-build için gerekli değil.

Diğer derleme sistemleri

CMake veya ndk-build kullanmıyorsanız derlemenizin belgelerine bakın ve sistemin bu özelliği etkinleştirmenin önerilen bir yolunun olup olmadığına bakın. Derlemeniz sistem bu seçeneği yerleşik olarak desteklemiyorsa özelliği derleme sırasında şu işaretleri iletmek:

-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability

İlki, NDK başlıklarını zayıf referanslara izin verecek şekilde yapılandırır. İkinci dönüş uyarısını bir hataya dönüştürür.

Daha fazla bilgi için Derleme Sistem Bakımı Kılavuzu'na bakın.

Korumalı API çağrıları

Bu özellik, sihirli bir şekilde yeni API'lere yapılan çağrıların güvenli olmasını sağlamaz. Projedeki tek şey bir yükleme süresi hatasını arama zamanı hatasına ertelemektir. Bunun avantajı, bu aramayı çalışma zamanında koruyabilir ve hatta alternatif bir uygulama ya da uygulamanın bu özelliğinin kullanıcılara veya bu kod yolundan tamamen kaçınabilir.

Clang, güvenlik önlemi yaptığınızda uyarı (unguarded-availability) yayınlayabilir uygulamanızın minSdkVersion cihazında kullanılamayan bir API'ye çağrı. Eğer veya CMake araç zinciri dosyamızı kullanırsanız bu uyarı otomatik olarak ve bu özellik etkinleştirilirken hataya yükseltildi.

Aşağıdaki örnekte, API'yi rastgele kullanan bu özellik, dlopen() ve dlsym() kullanılarak etkinleştirildi:

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);
    }
}

Okuması biraz karmaşık. Fonksiyon adları yineleniyor (ve C, imzaları da yazın) der, o zaman başarılı bir şekilde geçen fonksiyon adında yazım hatası yaparsanız çalışma zamanında yedeği alır dlsym olarak ayarlıyorsunuz ve her API için bu kalıbı kullanmanız gerekiyor.

Zayıf API referansları kullanıldığında yukarıdaki işlev şu şekilde yeniden yazılabilir:

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

Gelişmiş seçenekler, __builtin_available(android 31, *) aramaları android_get_device_api_level(), sonucu önbelleğe alır ve 31 ile karşılaştırır (AImageDecoder_resultToString()'ı kullanıma sunan API düzeyidir).

__builtin_available için hangi değerin kullanılacağını belirlemenin en basit yolu güvenlik duvarı veya güvenlik açığı __builtin_available(android 1, *)) ve hata mesajında belirtilen şeyi uygulayın. Örneğin, AImageDecoder_createFromAAsset() için güvenlik açığı içeren bir çağrı, minSdkVersion 24 şunu üretecek:

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

Bu durumda, arama __builtin_available(android 30, *) tarafından korunmalıdır. Yapı hatası yoksa minSdkVersion ve koruma gerekmez ya da derlemeniz yanlış yapılandırılmıştır. unguarded-availability uyarısı devre dışı bırakıldı.

Alternatif olarak, NDK API referansında şöyle bir şey söylenebilir: "API 30'da kullanıma sunuldu" tıklayın. Böyle bir metin yoksa bu, API, desteklenen tüm API düzeylerinde kullanılabilir.

API korumalarının tekrarından kaçınma

Bunu kullanıyorsanız uygulamanızda muhtemelen yalnızca yeterli sayıda yeni cihazda kullanılabilir. Aynı bilgileri tekrarlamak yerine __builtin_available() her bir fonksiyonunuzu kontrol ediyorsa kendi kodunuzu belirli bir API düzeyi gerektiriyor. Örneğin, ImageDecoder API'leri kendileri API 30'a eklendiğinden, bu kodların yoğun bir şekilde kullanıldığı işlevler için Yapabileceğiniz API'ler:

#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();
    }
}

İlginç API korumaları

Clang, __builtin_available'nin nasıl kullanıldığı konusunda son derece özeldir. Yalnızca düz bir değer (ancak büyük olasılıkla makroyla değiştirilmiştir) if (__builtin_available(...)) çalışır. Eşit if (!__builtin_available(...)) gibi basit işlemler çalışmaz (Clang hem de unsupported-availability-guard uyarısı verir. unguarded-availability). Bu, Clang'in gelecekteki bir sürümünde iyileştirilebilir. Görüntüleyin LLVM Sorunu 33161 başlıklı makaleyi inceleyin.

unguarded-availability kontrolleri yalnızca bu düzenlemelerin nedenlerinden biri. Clang, API çağrısına sahip işlev şu durumda olsa bile uyarı verir: hiçbir zaman sadece korumalı bir kapsam içinden çağrılmaz. Garsonların tekrarlanmasını önlemek için API korumalarının tekrarlanmasını önleme bölümüne bakın.

Bu neden varsayılan ayar değil?

Doğru kullanılmadığı sürece güçlü API referansları ile zayıf API arasındaki fark ilkinin hızla ve bariz bir şekilde başarısız olacağı, ancak kullanıcı, eksik API'ye neden olan bir işlem yapana kadar ikincisi başarısız olmaz. çağrılması gerekir. Bu durumda, hata mesajı net bir şekilde derleme zamanı "AFoo_bar() kullanılamıyor" bu bir hata olacaktır. Entegre güçlü referanslar varsa, hata mesajı çok daha nettir ve varsayılan olarak daha güvenlidir.

Bu yeni bir özellik olduğu için, kodu kullanmak için mevcut güvenli bir şekilde açıklayacağım. Android düşünülerek yazılmamış üçüncü taraf kod bu sorunu her zaman yaşayabilir; bu nedenle varsayılan davranışı tercih edebilirsiniz.

Daha fazla sorun oluşturacağı için bunu kullanmanızı öneririz ancak tespit edilmesi ve hatalarının ayıklanması zorsa bu riskleri bilginiz olmadan değişen davranışlardan daha iyidir.

Uyarılar

Bu özellik çoğu API'de işe yarar ancak iş yeri.

Sorunlu olabilecek en düşük tür, daha yeni libc API'leridir. Buradaki diğer Android API'leri (başlıklarda #if __ANDROID_API__ >= X ile korunur) hem de __INTRODUCED_IN(X) değil. Bu, zayıf beyanın bile görünür. API düzeyindeki en eski modern NDK desteği r21 olduğundan, yaygın olarak ihtiyaç duyulan libc API'leri mevcuttur. Her biri için yeni libc API'leri eklenir (status.md öğesine bakın) ancak ne kadar yeni olursa yalnızca az sayıda geliştiricinin ihtiyaç duyacağı uç bir örnek olacaktır. Bununla birlikte, paydaşlardan biri bu geliştiricileri çağırmak için şimdilik dlsym() uygulamasını kullanmaya devam etmeniz minSdkVersion değeri API'den daha eskiyse API'leri etkinleştirin. Bu, çözülebilir bir problemdir. Ancak bunu yapmak, tüm uygulamalar (tüm uygulamalar) için kaynak uyumluluğunu libc API'lerinin polyfill'lerini içeren kod libc ve yerel beyanlarda eşleşmeyen availability özellikleri), bu nedenle ya da ne zaman düzelteceğimizden emin değiliz.

Daha fazla geliştiriciyle karşılaşma olasılığı yüksek olan kitaplık, içeren yeni API, minSdkVersion değerinizden daha yeni. Yalnızca bu özellik zayıf sembol referanslarını mümkün kılar; kütüphanenin zayıf olması gibi bir şey yoktur bir referans noktası olarak kabul edilir. Örneğin, minSdkVersion değeri 24 ise libvulkan.so ve vkBindBufferMemory2 adlı kişiye güvenlikli bir arama yap, çünkü libvulkan.so, API 24 ile başlayan cihazlarda kullanılabilir. Diğer yandan minSdkVersion metriğiniz 23 ise dlopen ve dlsym uygulamalarına geri dönmeniz gerekir Çünkü kitaplık, yalnızca şu sürümleri destekleyen cihazlarda yer almaz: API 23. Bu durumu düzeltmek için iyi bir çözüm bilmiyoruz ama uzun vadede artık kendiliğinden çözülecektir, çünkü (mümkün olduğunda) bundan böyle yeni Yeni kitaplık oluşturma API'leri.

Kütüphane yazarları için

Android uygulamalarında kullanılmak üzere bir kitaplık geliştiriyorsanız bu özelliği herkese açık başlıklarınızda kullanmaktan kaçının. Güvenle kullanabileceğiniz yerler şunlardır: olabilir, ancak hesabınızdaki herhangi bir kodda __builtin_available kullanırsanız satır içi fonksiyonlar veya şablon tanımları gibi başlıklar kullanarak tüm öğelerinizi tüketicilerin bu özelliği etkinleştirmesine yardımcı olur. Aynı nedenlerden dolayı bunu varsayılan olarak NDK'da kullanmazsanız, müşteriniz adına bu seçimi yapmaktan kaçınmalısınız. yardımcı olur.

Herkese açık başlıklarınızda bu davranışa ihtiyaç duyarsanız hem de kullanıcılarınızın bu özelliği etkinleştirmeleri gerektiğini bilmelerini ve farkında olmanız gerekir.