Neuere APIs verwenden

Auf dieser Seite wird erläutert, wie deine App die neuen Funktionen des Betriebssystems nutzen kann, wenn sie auf einem neuen Betriebssystemversionen, wobei die Kompatibilität mit älteren Geräten beibehalten wird

Standardmäßig sind Verweise auf NDK APIs in Ihrer Anwendung aussagekräftige Referenzen. Das dynamische Ladeprogramm von Android wird diese Probleme schnellstmöglich auflösen, geladen. Werden die Symbole nicht gefunden, wird die App abgebrochen. Das steht im Widerspruch zu das Verhalten von Java, wobei erst dann eine Ausnahme ausgelöst wird, wenn die fehlende API aufgerufen.

Aus diesem Grund hindert das NDK Sie daran, starke Verweise auf APIs, die neuer als die minSdkVersion deiner App sind. So sind Sie vor versehentlicher Versandcode, der während des Tests funktioniert hat, aber nicht geladen wird (UnsatisfiedLinkError wird von System.loadLibrary() aus einer älteren Version gelöscht.) Geräte. Andererseits ist es schwieriger, Code zu schreiben, der APIs verwendet. neuer als das minSdkVersion Ihrer App, da Sie die APIs mit dlopen() und dlsym() anstelle eines normalen Funktionsaufrufs.

Die Alternative zur Verwendung starker Referenzen sind schwache Referenzen. Eine schwache Referenz, die nicht gefunden wird, wenn die geladene Bibliothek die Adresse von dieses Symbol auf nullptr gesetzt wird, anstatt zu laden. Sie waren noch immer kann nicht sicher angerufen werden, aber solange Anrufseiten darauf überwacht werden, Anrufe zu verhindern falls sie nicht verfügbar ist, können Sie den Rest Ihres Codes ausführen die API normal aufrufen, ohne dlopen() und dlsym() verwenden zu müssen.

Schwache API-Referenzen erfordern keine zusätzliche Unterstützung durch die dynamische Verknüpfung, sodass sie mit jeder Android-Version verwendet werden können.

Schwache API-Referenzen im Build aktivieren

CMake

Übergeben Sie -DANDROID_WEAK_API_DEFS=ON, wenn Sie CMake ausführen. Wenn Sie CMake über externalNativeBuild, fügen Sie Folgendes zu Ihrem build.gradle.kts (oder dem Grooviges Äquivalent, wenn Sie noch build.gradle verwenden):

android {
    // Other config...

    defaultConfig {
        // Other config...

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

NK-Build

Fügen Sie der Datei Application.mk Folgendes hinzu:

APP_WEAK_API_DEFS := true

Wenn Sie noch keine Application.mk-Datei haben, erstellen Sie sie im selben als Android.mk-Datei gespeichert. Weitere Änderungen an Ihrem build.gradle.kts- oder build.gradle-Dateien sind für ndk-build nicht erforderlich.

Andere Build-Systeme

Wenn Sie CMake oder ndk-build nicht verwenden, lesen Sie die Build-Dokumentation. um zu sehen, ob es eine Empfehlung zur Aktivierung dieser Funktion gibt. Wenn Ihr Build diese Option nativ nicht unterstützt wird, können Sie die Funktion aktivieren, indem Sie und übergeben beim Kompilieren folgende Flags:

-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability

Im ersten Schritt werden die NDK-Header so konfiguriert, dass schwache Referenzen zulässig sind. Die zweite Abbiegung die Warnung vor unsicheren API-Aufrufen in einen Fehler ein.

Weitere Informationen finden Sie im Build System Maintenanceers Guide (nur auf Englisch verfügbar).

Geschützte API-Aufrufe

Diese Funktion sorgt dafür, dass Aufrufe neuer APIs nicht sicher sind. Das Einzige, ist, einen Ladezeitfehler auf einen Aufruffehler zu übertragen. Der Vorteil ist, dass Sie kann diesen Aufruf während der Laufzeit absichern und reibungslose Fallbacks verhindern, sei es durch die Verwendung eines eine alternative Implementierung vorzuschlagen oder den Nutzer darüber zu informieren, dass diese Funktion der App nicht auf ihrem Gerät verfügbar sind, oder sie umgehen diesen Codepfad gänzlich.

Clang kann eine Warnung ausgeben (unguarded-availability), wenn du eine ungeschützte -Aufruf an eine API, die für die minSdkVersion deiner App nicht verfügbar ist. Wenn Sie mit ndk-build oder unserer CMake-Toolchain-Datei, wird diese Warnung automatisch aktiviert und beim Aktivieren dieser Funktion zu einem Fehler hochgestuft.

Hier ist ein Beispiel für Code, der eine bedingte API ohne Diese Funktion wurde mit dlopen() und dlsym() aktiviert:

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

Es ist etwas chaotisch zu lesen, es gibt doppelte Funktionsnamen (und C schreiben Sie auch die Signaturen), wird es zwar erfolgreich erstellt, aber immer Das Fallback zur Laufzeit verwenden, wenn Sie den übergebenen Funktionsnamen versehentlich vertippen auf dlsym und Sie müssen dieses Muster für jede API verwenden.

Mit schwachen API-Referenzen kann die obige Funktion wie folgt umgeschrieben werden:

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

Im Hintergrund ruft __builtin_available(android 31, *) an android_get_device_api_level(), speichert das Ergebnis im Cache und vergleicht es mit 31 (die API-Ebene, mit der AImageDecoder_resultToString() eingeführt wurde).

Die einfachste Methode, um zu ermitteln, welchen Wert für __builtin_available verwendet werden soll, ist die ohne die Wache (oder die Wache __builtin_available(android 1, *)) und folge der Fehlermeldung. Beispiel: Ein unbeaufsichtigter Anruf an AImageDecoder_createFromAAsset() mit minSdkVersion 24 wird Folgendes erzeugen:

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

In diesem Fall sollte der Anruf durch __builtin_available(android 30, *) überwacht werden. Wenn kein Build-Fehler vorliegt, ist entweder die API immer für Ihre minSdkVersion und kein Guard ist erforderlich oder Ihr Build ist falsch konfiguriert und der unguarded-availability-Warnung ist deaktiviert.

Alternativ wird in der NDK API-Referenz etwas Ähnliches angegeben. „In API 30 eingeführt“ für jede API. Wenn dieser Text nicht vorhanden ist, bedeutet das, ist die API für alle unterstützten API-Ebenen verfügbar.

Wiederholung von API-Guards vermeiden

Wenn Sie diese Option verwenden, werden Sie wahrscheinlich Code-Abschnitte in Ihrer App haben, die nur auf Geräten nutzbar sind, die gerade neu genug sind. Anstatt die __builtin_available() in jeder Ihrer Funktionen einchecken, können Sie Ihre dass ein bestimmtes API-Level erforderlich ist. Zum Beispiel können die ImageDecoder APIs wurden in API 30 hinzugefügt. Für Funktionen, die diese Funktionen APIs können Sie beispielsweise Folgendes ausführen:

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

Quirks von API-Guards

Bei Clang wird besonders __builtin_available verwendet. Nur ein Literal if (__builtin_available(...)) funktioniert, ist jedoch möglicherweise durch eine Ersetzung mit Makros ersetzt. Gleichmäßig Einfache Vorgänge wie if (!__builtin_available(...)) funktionieren nicht (Clang, gibt die Warnung unsupported-availability-guard sowie unguarded-availability. Dies wird sich in einer zukünftigen Version von Clang möglicherweise verbessern. Weitere Informationen finden Sie unter Weitere Informationen finden Sie unter LLVM-Problem 33161.

Die Prüfung auf unguarded-availability gilt nur für den Funktionsbereich, in dem sie verwendet werden. Clang gibt die Warnung auch dann aus, wenn die Funktion mit dem API-Aufruf immer nur aus einem überwachten Bereich aufgerufen. Um die Wiederholung von Guards in Ihren eigenen Code finden Sie unter Wiederholung von API-Guards vermeiden.

Warum ist das nicht die Standardeinstellung?

Der Unterschied zwischen starken und schwachen API-Referenzen bei richtiger Verwendung dass erstes schnell und offensichtlich fehlschlägt, während das für Letztere schlägt erst fehl, wenn der Nutzer eine Aktion durchführt, die die fehlende API verursacht. aufgerufen werden soll. In diesem Fall ist die Fehlermeldung nicht eindeutig Compile-Zeit "AFoo_bar() is not available" ist es ein Segmentierungsfehler. Mit sind die Fehlermeldungen viel klarer und "Failing-fast" ist ein für mehr Sicherheit.

Da dies eine neue Funktion ist, wird nur sehr wenig Code geschrieben, auf sichere Weise. Drittanbietercode, der nicht speziell für Android geschrieben wurde wird dieses Problem wahrscheinlich immer auftreten. Daher gibt es derzeit keine Pläne für die Standardverhalten ändern.

Wir empfehlen, dass ihr diese Methode verwendet, aber da sie die Probleme erschweren wird schwer zu erkennen und zu beheben, sollten Sie diese Risiken wissentlich akzeptieren, als das Verhalten, das sich ohne Ihr Wissen ändert.

Einschränkungen

Diese Funktion funktioniert mit den meisten APIs, es gibt jedoch einige Fälle, in denen sie nicht arbeiten.

Am wenigsten problematisch sind neuere libc-APIs. Im Gegensatz zu den anderen Android-APIs, die durch #if __ANDROID_API__ >= X in den Headern geschützt werden und nicht nur __INTRODUCED_IN(X), wodurch auch die schwache Deklaration gesehen wird. Da die älteste API-Level-Unterstützung moderner NDKs r21 ist, häufig benötigte libc APIs sind bereits verfügbar. Neue libc APIs werden jeweils hinzugefügt (siehe status.md). Je neuer sie jedoch sind, desto wahrscheinlicher ist es, dass sie ein Grenzfall sein, den nur wenige Entwickelnde benötigen. Wenn Sie jedoch Für diese Entwickler müssen Sie vorerst weiterhin dlsym() verwenden, um diese aufzurufen. APIs, wenn minSdkVersion älter als die API ist. Das ist ein lösbares Problem, Dies birgt jedoch das Risiko, dass die Kompatibilität der Quelle für alle Apps (alle Code, der polyfills von libc-APIs enthält, kann aufgrund des Fehlers availability-Attribute in der libc- und lokalen Deklaration nicht übereinstimmen. sind wir uns nicht sicher, ob oder wann wir das Problem beheben werden.

Mehr Entwickler werden wahrscheinlich begegnen, wenn die library, die enthält die neue API neuer als Ihr minSdkVersion. Nur diese Funktion aktiviert schwache Symbolverweise; gibt es keine schwache Bibliothek Referenz. Wenn Ihre minSdkVersion beispielsweise 24 ist, können Sie libvulkan.so und sende einen überwachten Anruf an vkBindBufferMemory2, weil libvulkan.so ist für Geräte ab API 24 verfügbar. Im Gegensatz dazu Wenn Ihre minSdkVersion 23 war, müssen Sie auf dlopen und dlsym zurückgreifen da die Bibliothek auf dem Gerät nicht auf Geräten vorhanden ist, API 23. Wir kennen keine gute Lösung zum Beheben dieses Falls, aber wir haben lange Zeit löst sich von selbst, da wir (wenn möglich) keine neuen APIs zum Erstellen neuer Bibliotheken.

Für Bibliotheksautoren

Wenn Sie eine Bibliothek für die Verwendung in Android-Apps entwickeln, sollten Sie Vermeiden Sie die Verwendung dieser Funktion in Ihren öffentlichen Headern. Es kann sicher verwendet werden in Out-of-Line-Code. Wenn Sie jedoch in irgendeinem Code in Ihrem__builtin_available wie Inline-Funktionen oder Vorlagendefinitionen, erzwingen Sie alle Ihre um diese Funktion zu aktivieren. Aus denselben Gründen aktivieren wir auch standardmäßig im NDK festgelegt ist, sollten Sie diese Auswahl nicht im Namen des Ihrer Kunden.

Wenn Sie dies in Ihren öffentlichen Headern benötigen, dokumentieren Sie sodass die Nutzenden wissen, dass sie die Funktion aktivieren müssen, sich der Risiken bewusst sind.