Porady dla dostawców oprogramowania pośredniczącego

Rozpowszechnianie oprogramowania pośredniczącego utworzonego w ramach pakietu NDK wiąże się z kilkoma dodatkowymi problemami, którymi deweloperzy aplikacji nie muszą się martwić. Gotowe biblioteki narzucają użytkownikom niektóre opcje implementacji.

Wybieranie poziomów interfejsu API i wersji NDK

Twoi użytkownicy nie mogą używać wartości minSdkVersion niższej niż Twoja. Jeśli aplikacje Twoich użytkowników muszą korzystać z interfejsu API 21, nie możesz tworzyć kompilacji pod kątem tego interfejsu. Możesz tworzyć biblioteki na niższym poziomie interfejsu API niż użytkownicy. Możesz tworzyć kompilacje dla interfejsu API 16 i zachować zgodność z użytkownikami interfejsu API 21.

Wersje NDK są w większości zgodne ze sobą, ale czasami zdarzają się zmiany, które zakłócają zgodność. Jeśli wiesz, że wszyscy użytkownicy korzystają z tej samej wersji pakietu NDK, najlepiej jest używać tej samej wersji, co oni. W przeciwnym razie używaj najnowszej wersji.

Używanie STL

Jeśli piszesz w języku C++ i używasz STL, wybór między libc++_shared a libc++_static będzie miał wpływ na Twoich użytkowników, jeśli rozpowszechniasz zasoby udostępniane. Jeśli rozpowszechniasz zasoby udostępniane, musisz użyć zasady libc++_shared lub upewnić się, że symbole z biblioteki libc++ nie są widoczne w bibliotece. Najlepiej zadeklarować platformę ABI za pomocą skryptu wersji (pomoże to także zachować prywatność szczegółów implementacji). Na przykład prosta biblioteka arytmetyczna może mieć taki skrypt wersji:

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:
    *;
};

Skrypt wersji powinien być opcją preferowaną, ponieważ jest to najpewniejszy sposób kontrolowania widoczności symboli. Jest to sprawdzona metoda w przypadku wszystkich bibliotek wspólnych oraz oprogramowania pośredniczącego, ponieważ zapobiega ujawnieniu szczegółów implementacji i skraca czas wczytywania.

Inną, mniej niezawodną opcją, jest użycie elementu -Wl,--exclude-libs,libc++_static.a -Wl,--exclude-libs,libc++abi.a do łączenia. Jest to mniej niezawodne, ponieważ ukrywa tylko symbole w bibliotekach, które mają jawne nazwy, a w przypadku nieużywanych bibliotek nie są zgłaszane żadne dane diagnostyczne (literówka w nazwie biblioteki nie jest błędem, a koniecznością aktualizowania listy bibliotek jest użytkownik). W ten sposób nie ukryjesz też szczegółów Twojej implementacji.

Rozpowszechnianie bibliotek natywnych w AAR

Wtyczka Androida do obsługi Gradle może importować zależności natywne rozpowszechniane w plikach AAR. Jeśli Twoi użytkownicy używają wtyczki Androida do obsługi Gradle, jest to najprostszy sposób na korzystanie z Twojej biblioteki.

Biblioteki natywne można spakować do AAR w AGP. Jest to najprostsza opcja, jeśli biblioteka została już skompilowana za pomocą externalNativeBuild.

Kompilacje w środowisku innym niż AGP mogą korzystać z ndkports lub ręcznie tworzyć pakowanie, postępując zgodnie z dokumentacją Prefab i tworząc podkatalog prefab/ pliku AAR.

Oprogramowanie pośredniczące w Javie z bibliotekami JNI

Biblioteki Java zawierające biblioteki JNI (czyli biblioteki AAR zawierające jniLibs) muszą zadbać o to, aby zawarte w nich biblioteki JNI nie kolidowały z innymi bibliotekami w aplikacji użytkownika. Jeśli na przykład AAR zawiera libc++_shared.so, ale inna wersja libc++_shared.so niż używana przez aplikację, w pakiecie APK zostanie zainstalowana tylko jedna, co może prowadzić do niestabilnego działania.

Najbardziej niezawodnym rozwiązaniem jest dodanie do bibliotek Javy nie więcej niż jednej biblioteki JNI (to też dobra rada w przypadku aplikacji). Wszystkie zależności, w tym STL, powinny być statycznie połączone z biblioteką implementacji, a do wymuszania platformy ABI należy używać skryptu wersji. Na przykład biblioteka Java com.example.foo, która zawiera bibliotekę JNI libfooimpl.so, powinna używać tego skryptu:

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

W tym przykładzie użyto protokołu registerNatives za pomocą JNI_OnLoad, zgodnie ze wskazówkami dotyczącymi JNI, aby odsłonić minimalną powierzchnię interfejsu ABI i skrócić czas wczytywania biblioteki.