Tipps für Middleware-Anbieter

Die Verteilung der mit dem NDK erstellten Middleware wirft einige zusätzliche Probleme auf, über die sich App-Entwickler keine Gedanken machen müssen. Bei vordefinierten Bibliotheken sind den Nutzern einige Implementierungsoptionen auferlegt.

API-Ebenen und NDK-Versionen auswählen

Ihre Nutzer können keine niedrigere minSdkVersion als Ihre verwenden. Wenn die Anwendungen Ihrer Nutzer auf API 21 ausgeführt werden müssen, können Sie nicht für API 24 entwickeln. Sie können Ihre Bibliothek für eine niedrigere API-Ebene als die Ihrer Nutzer erstellen. Sie können Apps für API 16 entwickeln und dabei mit Ihren API 21-Nutzern kompatibel bleiben.

NDK-Versionen sind weitgehend miteinander kompatibel, aber gelegentlich gibt es Änderungen, die die Kompatibilität beeinträchtigen. Wenn Sie wissen, dass alle Ihre Nutzer dieselbe Version des NDK verwenden, sollten Sie dieselbe Version wie sie verwenden. Andernfalls sollten Sie die neueste Version verwenden.

STL verwenden

Wenn Sie C++ schreiben und den STL verwenden, wirkt sich die Auswahl zwischen libc++_shared und libc++_static auf Ihre Nutzer aus, wenn Sie eine gemeinsam genutzte Bibliothek verteilen. Wenn Sie eine gemeinsam genutzte Bibliothek bereitstellen, müssen Sie entweder libc++_shared verwenden oder dafür sorgen, dass die Symbole von libc++ nicht von Ihrer Bibliothek verfügbar gemacht werden. Die beste Möglichkeit dazu besteht darin, Ihre ABI-Oberfläche explizit mit einem Versionsskript zu deklarieren (dies trägt auch dazu bei, Ihre Implementierungsdetails zu schützen). Eine einfache arithmetische Bibliothek könnte beispielsweise das folgende Versionsskript haben:

LIBMYMATH {
global:
    add;
    sub;
    mul;
    div;
    # C++ symbols in an extern block will be mangled automatically. See
    # https://stackoverflow.com/a/21845178/632035 for more examples.
    extern "C++" {
        "pow(int, int)";
    }
local:
    *;
};

Ein Versionsskript sollte die bevorzugte Option sein, da es die zuverlässigste Möglichkeit ist, die Symbolsichtbarkeit zu steuern. Dies ist eine Best Practice für alle freigegebenen Bibliotheken, ob Middleware oder nicht, da es verhindert, dass Ihre Implementierungsdetails offengelegt werden, und verbessert die Ladezeit.

Eine weitere, weniger zuverlässige Option ist die Verwendung von -Wl,--exclude-libs,libc++_static.a -Wl,--exclude-libs,libc++abi.a bei der Verknüpfung. Dies ist weniger robust, da nur die Symbole in den Bibliotheken ausgeblendet werden, die explizit benannt sind. Für nicht verwendete Bibliotheken werden keine Diagnosen gemeldet. Ein Tippfehler im Bibliotheksnamen ist kein Fehler und der Nutzer muss die Bibliotheksliste auf dem neuesten Stand halten. Dabei werden auch Ihre eigenen Implementierungsdetails nicht verborgen.

Native Bibliotheken in AAE verteilen

Das Android-Gradle-Plug-in kann native Abhängigkeiten importieren, die in AAE verteilt sind. Wenn Ihre Nutzer das Android-Gradle-Plug-in verwenden, ist dies die einfachste Möglichkeit, auf Ihre Bibliothek zuzugreifen.

Native Bibliotheken können von AGP zu einem AAE verpackt werden. Dies ist die einfachste Option, wenn Ihre Bibliothek bereits von externalNativeBuild erstellt wurde.

Nicht-AGP-Builds können ndkports verwenden oder ein manuelles Packen ausführen. Folgen Sie dazu der Prefab-Dokumentation, um das Unterverzeichnis prefab/ des AAEs zu erstellen.

Java-Middleware mit JNI-Bibliotheken

Bei Java-Bibliotheken, die JNI-Bibliotheken enthalten (d. h. AARs, die jniLibs enthalten), muss darauf geachtet werden, dass sich die darin enthaltenen JNI-Bibliotheken nicht mit anderen Bibliotheken in der App des Nutzers kollidieren. Wenn beispielsweise libc++_shared.so, aber eine andere Version von libc++_shared.so als die App verwendet wird, wird nur eine im APK installiert. Dies kann zu unzuverlässigem Verhalten führen.

Die zuverlässigste Lösung ist, dass Java-Bibliotheken nicht mehr als eine JNI-Bibliothek enthalten. Auch für Anwendungen empfiehlt sich dies. Alle Abhängigkeiten, einschließlich des STL, sollten statisch mit der Implementierungsbibliothek verknüpft sein. Außerdem sollte ein Versionsskript verwendet werden, um die ABI-Oberfläche zu erzwingen. Für eine Java-Bibliothek com.example.foo, die die JNI-Bibliothek libfooimpl.so enthält, sollte beispielsweise das folgende Versionsskript verwendet werden:

LIBFOOIMPL {
global:
    JNI_OnLoad;
local:
    *;
};

In diesem Beispiel wird registerNatives über JNI_OnLoad verwendet, wie in den JNI-Tipps beschrieben, damit die minimale ABI-Oberfläche verfügbar ist und die Ladezeit der Bibliothek minimiert wird.