Typowe problemy i ich rozwiązania

Ten dokument zawiera częściową listę najczęstszych problemów, które nie są błędami, a które mogą wystąpić podczas korzystania z NDK, oraz ich rozwiązania (jeśli są dostępne).

Używanie _FILE_OFFSET_BITS=64 na starszych poziomach interfejsu API

Przed wprowadzeniem ujednoliconych nagłówków NDK nie obsługiwał _FILE_OFFSET_BITS=64. Jeśli zdefiniowano go podczas tworzenia aplikacji, został on zignorowany. Opcja _FILE_OFFSET_BITS=64 jest teraz obsługiwana w przypadku ujednoliconych nagłówków, ale w starszych wersjach Androida bardzo niewiele interfejsów API off_t było dostępnych jako wariant off64_t. Dlatego używanie tej funkcji na starszych poziomach interfejsu API powoduje, że dostępnych jest mniej funkcji.

Ten problem jest szczegółowo opisany w poście na blogu r16 i w dokumentacji bionic.

Problem: kompilacja wymaga interfejsów API, które nie istnieją w minSdkVersion.

Rozwiązanie: wyłącz _FILE_OFFSET_BITS=64 lub zwiększ minSdkVersion.

Niezadeklarowana lub domyślna definicja mmap

W C++ może pojawić się ten błąd:

error: use of undeclared identifier 'mmap'

lub ten błąd w C:

warning: implicit declaration of function 'mmap' is invalid in C99

Użycie _FILE_OFFSET_BITS=64 powoduje, że biblioteka C używa mmap64 zamiast mmap. mmap64 był dostępny dopiero od android-21. Jeśli wartość minSdkVersion jest niższa niż 21, biblioteka C nie zawiera mmap, który jest zgodny z _FILE_OFFSET_BITS=64, więc funkcja jest niedostępna.

minSdkVersion ustawiony na wyższy poziom interfejsu API niż poziom interfejsu API urządzenia

Poziom interfejsu API, na którym tworzysz aplikację za pomocą NDK, ma zupełnie inne znaczenie niż compileSdkVersion w przypadku Javy. Poziom interfejsu API NDK to minimalny obsługiwany poziom interfejsu API aplikacji. W ndk-build jest to ustawienie APP_PLATFORM. W CMake jest to -DANDROID_PLATFORM.

Ponieważ odwołania do funkcji są zwykle rozwiązywane podczas wczytywania bibliotek, a nie podczas pierwszego wywołania, nie możesz odwoływać się do interfejsów API, które nie są zawsze obecne, i chronić ich użycia za pomocą sprawdzania poziomu interfejsu API. Jeśli są one w ogóle przywoływane, muszą być obecne.

Problem: poziom interfejsu API NDK jest wyższy niż poziom interfejsu API obsługiwany przez urządzenie.

Rozwiązanie: ustaw poziom interfejsu API NDK (APP_PLATFORM) na minimalną wersję Androida obsługiwaną przez aplikację.

System kompilacji Ustawienie
ndk-build APP_PLATFORM
CMake ANDROID_PLATFORM
externalNativeBuild android.minSdkVersion

W przypadku innych systemów kompilacji zapoznaj się z artykułem Korzystanie z NDK w innych systemach kompilacji.

Nie można znaleźć symboli __aeabi

Ten komunikat:

UnsatisfiedLinkError: dlopen failed: cannot locate symbol "__aeabi_memcpy"

jest jednym z przykładów możliwych błędów czasu wykonywania. Te błędy pojawiają się w logu, gdy próbujesz wczytać biblioteki natywne. Symbolem może być dowolny symbol __aeabi_*; najczęstsze to __aeabi_memcpy i __aeabi_memclr.

Ten problem jest opisany w artykule Issue 126.

Nie można znaleźć symbolu rand

W przypadku tego komunikatu logu o błędzie:

UnsatisfiedLinkError: dlopen failed: cannot locate symbol "rand"

Zapoznaj się z tą szczegółową odpowiedzią na Stack Overflow.

Niezdefiniowane odwołanie do __atomic_*

Problem: niektóre ABI wymagają libatomic, aby zapewnić implementacje operacji atomowych.

Rozwiązanie: podczas łączenia dodaj -latomic.

W przypadku tego komunikatu o błędzie:

error: undefined reference to '__atomic_exchange_4'

rzeczywistym symbolem może być dowolny symbol z prefiksem __atomic_.

RTTI/wyjątki nie działają poza granicami biblioteki

Problem: wyjątki nie są przechwytywane, gdy są zgłaszane poza granicami biblioteki współdzielonej , lub dynamic_cast kończy się niepowodzeniem.

Rozwiązanie: dodaj do typów funkcję klucza. Funkcja klucza to pierwsza funkcja wirtualna typu, która nie jest czysto wirtualna i nie jest wbudowana. Przykład znajdziesz w dyskusji na temat Issue 533.

ABI C++ stwierdza, że 2 obiekty mają ten sam typ tylko wtedy, gdy ich type_info wskaźniki są identyczne. Wyjątki można przechwytywać tylko wtedy, gdy type_info dla przechwycenia pasuje do zgłoszonego wyjątku. Ta sama reguła dotyczy dynamic_cast.

Gdy typ nie ma funkcji klucza, jego typeinfo jest emitowany jako słaby symbol, a pasujące informacje o typie są scalane podczas wczytywania bibliotek. Podczas dynamicznego wczytywania bibliotek po wczytaniu pliku wykonywalnego (czyli za pomocą dlopen lub System.loadLibrary) moduł wczytujący może nie być w stanie scalić informacji o typie wczytanych bibliotek. W takim przypadku 2 typy nie są uważane za równe.

Używanie niezgodnych wstępnie skompilowanych bibliotek

Używanie w aplikacji wstępnie skompilowanych bibliotek (zwykle bibliotek innych firm) wymaga dodatkowej uwagi. Ogólnie rzecz biorąc, pamiętaj o tych regułach:

  • Minimalny poziom interfejsu API wynikowej aplikacji jest maksymalnym poziomem minSdkVersion wszystkich bibliotek aplikacji.

    Jeśli minSdkVersion to 16, ale używasz wstępnie skompilowanej biblioteki, która została skompilowana na poziomie 21, minimalny poziom interfejsu API wynikowej aplikacji to 21. Niestosowanie się do tej zasady będzie widoczne podczas kompilacji, jeśli wstępnie skompilowana biblioteka jest statyczna, ale może się nie pojawić aż do czasu wykonywania w przypadku wstępnie skompilowanych bibliotek współdzielonych.

  • Wszystkie biblioteki powinny być generowane w tej samej wersji NDK.

    Ta reguła jest nieco bardziej elastyczna niż większość, ponieważ rzadko dochodzi do awarii, ale zgodność między bibliotekami, które zostały skompilowane w różnych głównych wersjach NDK, nie jest gwarantowana. ABI C++ nie jest stabilne i w przeszłości ulegało zmianom.

  • Aplikacje z wieloma bibliotekami współdzielonymi muszą używać współdzielonego STL.

    Podobnie jak w przypadku niezgodnych STL, problemów spowodowanych przez to można uniknąć, jeśli zachowasz dużą ostrożność, ale lepiej po prostu ich unikać. Najlepszym sposobem na uniknięcie tego problemu jest unikanie używania w aplikacji wielu bibliotek współdzielonych.