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.