Konfigurowanie CMake

Skrypt kompilacji CMake to zwykły plik tekstowy o nazwie CMakeLists.txt. Zawiera on polecenia, których używa CMake do tworzenia bibliotek C/C++. Jeśli Twoje źródła natywne nie mają jeszcze skryptu kompilacji CMake, musisz go utworzyć samodzielnie i dołączyć odpowiednie polecenia CMake. Aby dowiedzieć się, jak zainstalować CMake, przeczytaj artykuł o instalowaniu i konfigurowaniu NDK i CMake.

W tej sekcji omawiamy podstawowe polecenia, które należy uwzględnić w skrypcie kompilacji, aby wskazać CMake, które źródła mają być używane podczas tworzenia biblioteki natywnej. Więcej informacji znajdziesz w oficjalnej dokumentacji poleceń CMake.

Po skonfigurowaniu nowego skryptu kompilacji CMake musisz skonfigurować Gradle tak, aby uwzględnić projekt CMake jako zależność kompilacji. Dzięki temu Gradle skompiluje i skompiluje bibliotekę natywną z pakietem APK aplikacji.

Uwaga: jeśli Twój projekt korzysta z narzędzia ndk-build, nie musisz tworzyć skryptu kompilacji CMake. Możesz po prostu skonfigurować Gradle, aby uwzględnić istniejący projekt biblioteki natywnej, podając ścieżkę do pliku Android.mk.

Tworzenie skryptu kompilacji CMake

Aby utworzyć plik tekstowy, którego możesz użyć jako skryptu kompilacji CMake, wykonaj te czynności:

  1. Otwórz panel Projekt z lewej strony IDE i wybierz widok Projekt z menu.
  2. Kliknij prawym przyciskiem myszy katalog główny your-module i wybierz Nowy > Plik.

    Uwaga: skrypt kompilacji możesz utworzyć w dowolnym miejscu. Jednak podczas konfigurowania skryptu kompilacji ścieżki do natywnych plików źródłowych i bibliotek są zależne od jego lokalizacji.

  3. Jako nazwę pliku wpisz „CMakeLists.txt” i kliknij OK.

Możesz teraz skonfigurować skrypt kompilacji, dodając polecenia CMake. Aby polecić CMake utworzenie biblioteki natywnej na podstawie natywnego kodu źródłowego, dodaj do skryptu kompilacji polecenia cmake_minimum_required() i add_library():

# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.

cmake_minimum_required(VERSION 3.4.1)

# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add_library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.

add_library( # Specifies the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

Wskazówka: podobnie jak w przypadku polecenia CMake, aby utworzył on bibliotekę natywną na podstawie plików źródłowych, możesz użyć polecenia add_executable(), aby wskazać CMake utworzenie pliku wykonywalnego na podstawie tych plików źródłowych. Tworzenie plików wykonywalnych ze źródeł natywnych jest jednak opcjonalne, a tworzenie bibliotek natywnych do spakowania do pliku APK spełnia większość wymagań projektów.

Gdy dodasz plik źródłowy lub bibliotekę do skryptu kompilacji CMake za pomocą add_library(), po zsynchronizowaniu projektu Android Studio wyświetli też powiązane pliki nagłówka w widoku Projekt. Aby jednak CMake mógł zlokalizować pliki nagłówka podczas kompilacji, musisz dodać polecenie include_directories() do skryptu kompilacji CMake i określić ścieżkę do nagłówków:

add_library(...)

# Specifies a path to native header files.
include_directories(src/main/cpp/include/)

Konwencja używana przez CMake do nazwania pliku biblioteki jest taka:

liblibrary-name.so

Jeśli na przykład w skrypcie kompilacji podasz „native-lib” jako nazwę biblioteki udostępnionej, CMake utworzy plik o nazwie libnative-lib.so. Jednak podczas wczytywania tej biblioteki w kodzie Java lub Kotlin użyj nazwy podanej w skrypcie kompilacji CMake:

Kotlin

companion object {
    init {
        System.loadLibrary("native-lib");
    }
}

Java

static {
    System.loadLibrary("native-lib");
}

Uwaga: jeśli zmienisz nazwę biblioteki w skrypcie kompilacji CMake lub usuniesz ją, musisz wyczyścić projekt, zanim Gradle zastosuje zmiany lub usunie starszą wersję biblioteki z pliku APK. Aby wyczyścić projekt, z paska menu wybierz Kompilacja > Oczyść projekt.

Android Studio automatycznie dodaje pliki źródłowe i nagłówki do grupy cpp w panelu Projekt. Za pomocą wielu poleceń add_library() możesz zdefiniować dodatkowe biblioteki, które CMake będzie tworzyć na podstawie innych plików źródłowych.

Dodaj interfejsy API NDK

Pakiet Android NDK zawiera zestaw natywnych interfejsów API i bibliotek, które mogą Ci się przydać. Możesz używać dowolnego z tych interfejsów API, dodając biblioteki NDK do pliku skryptu CMakeLists.txt projektu.

Gotowe biblioteki NDK są już dostępne na platformie Android, więc nie musisz ich kompilować ani pakować do pliku APK. Biblioteki NDK są już częścią ścieżki wyszukiwania CMake, więc nie musisz nawet określać ich lokalizacji w lokalnej instalacji NDK – wystarczy podać w narzędziu CMake tylko nazwę biblioteki, której chcesz używać, i połączyć ją z Twoją biblioteką natywną.

Dodaj polecenie find_library() do skryptu kompilacji CMake, aby znaleźć bibliotekę NDK i zapisać ścieżkę jako zmienną. Tej zmiennej możesz używać, aby odwoływać się do biblioteki NDK w innych częściach skryptu kompilacji. Poniższy przykład pozwala zlokalizować bibliotekę obsługi logów dotyczącą Androida i zapisać jej ścieżkę w pliku log-lib:

find_library( # Defines the name of the path variable that stores the
              # location of the NDK library.
              log-lib

              # Specifies the name of the NDK library that
              # CMake needs to locate.
              log )

Aby biblioteka natywna wywoływała funkcje w bibliotece log, musisz połączyć biblioteki za pomocą polecenia target_link_libraries() w skrypcie kompilacji CMake:

find_library(...)

# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the log library to the target library.
                       ${log-lib} )

Pakiet NDK zawiera też kilka bibliotek jako kod źródłowy, który musisz utworzyć i połączyć z biblioteką natywną. Kod źródłowy możesz skompilować do biblioteki natywnej za pomocą polecenia add_library() w skrypcie kompilacji CMake. Aby podać ścieżkę do lokalnej biblioteki NDK, możesz użyć zmiennej ścieżki ANDROID_NDK, która jest automatycznie definiowana przez Android Studio.

To polecenie instruuje CMake, aby skompilował zasób android_native_app_glue.c, który zarządza zdarzeniami cyklu życia NativeActivity i wprowadzaniem dotykowym, do biblioteki statycznej i łączy ją z biblioteką native-lib:

add_library( app-glue
             STATIC
             ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )

# You need to link static libraries against your shared native library.
target_link_libraries( native-lib app-glue ${log-lib} )

Dodaj inne gotowe biblioteki

Dodawanie gotowej biblioteki przypomina określenie innej biblioteki natywnej do utworzenia w CMake. Ponieważ jednak biblioteka jest już skompilowana, musisz użyć flagi IMPORTED, aby poinformować CMake, że chcesz zaimportować ją tylko do projektu:

add_library( imported-lib
             SHARED
             IMPORTED )

Następnie musisz podać ścieżkę do biblioteki za pomocą polecenia set_target_properties(), jak pokazano poniżej.

Niektóre biblioteki udostępniają osobne pakiety dla określonych architektur procesora lub interfejsów binarnych aplikacji (ABI) i organizują je w osobnych katalogach. Takie podejście pomaga biblioteczkom korzystać z określonej architektury procesora, umożliwiając używanie tylko tych wersji biblioteki, których potrzebujesz. Aby dodać wiele wersji biblioteki ABI do skryptu kompilacji CMake bez konieczności pisania wielu poleceń dla każdej wersji biblioteki, możesz użyć zmiennej ścieżki ANDROID_ABI. Ta zmienna używa listy domyślnych interfejsów ABI obsługiwanych przez NDK lub filtrowanej listy interfejsów ABI ręcznie skonfigurowanych w Gradle. Na przykład:

add_library(...)
set_target_properties( # Specifies the target library.
                       imported-lib

                       # Specifies the parameter you want to define.
                       PROPERTIES IMPORTED_LOCATION

                       # Provides the path to the library you want to import.
                       imported-lib/src/${ANDROID_ABI}/libimported-lib.so )

Aby usługa CMake mogła zlokalizować pliki nagłówków podczas kompilacji, musisz użyć polecenia include_directories() i podać ścieżkę do plików nagłówka:

include_directories( imported-lib/include/ )

Uwaga: jeśli chcesz spakować gotową bibliotekę, która nie jest zależność w czasie kompilacji, np. dodając gotową bibliotekę zależną od imported-lib, nie musisz wykonywać poniższych instrukcji, aby ją połączyć.

Aby połączyć gotową bibliotekę z własną biblioteką natywną, dodaj ją do polecenia target_link_libraries() w skrypcie kompilacji CMake:

target_link_libraries( native-lib imported-lib app-glue ${log-lib} )

Aby spakować gotową bibliotekę do pliku APK, musisz ręcznie skonfigurować Gradle z blokiem sourceSets, aby uwzględnić ścieżkę do pliku .so. Po utworzeniu pliku APK możesz sprawdzić, które biblioteki pakietu Gradle do niego dołączają, korzystając z Analizatora plików APK.

Uwzględnij inne projekty CMake

Jeśli chcesz utworzyć wiele projektów CMake i uwzględnić ich dane wyjściowe w projekcie Androida, możesz użyć 1 pliku CMakeLists.txt jako skryptu kompilacji CMake najwyższego poziomu (którego połączysz z Gradle) i dodać kolejne projekty CMake jako zależności od tego skryptu. Poniższy skrypt kompilacji CMake najwyższego poziomu korzysta z polecenia add_subdirectory(), aby określić inny plik CMakeLists.txt jako zależność kompilacji, a następnie łączy się z jego danymi wyjściowymi tak samo jak w przypadku każdej innej gotowej biblioteki.

# Sets lib_src_DIR to the path of the target CMake project.
set( lib_src_DIR ../gmath )

# Sets lib_build_DIR to the path of the desired output directory.
set( lib_build_DIR ../gmath/outputs )
file(MAKE_DIRECTORY ${lib_build_DIR})

# Adds the CMakeLists.txt file located in the specified directory
# as a build dependency.
add_subdirectory( # Specifies the directory of the CMakeLists.txt file.
                  ${lib_src_DIR}

                  # Specifies the directory for the build outputs.
                  ${lib_build_DIR} )

# Adds the output of the additional CMake build as a prebuilt static
# library and names it lib_gmath.
add_library( lib_gmath STATIC IMPORTED )
set_target_properties( lib_gmath PROPERTIES IMPORTED_LOCATION
                       ${lib_build_DIR}/${ANDROID_ABI}/lib_gmath.a )
include_directories( ${lib_src_DIR}/include )

# Links the top-level CMake build output against lib_gmath.
target_link_libraries( native-lib ... lib_gmath )

Wywoływanie CMake z wiersza poleceń

Użyj tego polecenia, by wywołać CMake w celu wygenerowania projektu Ninja poza Android Studio:

cmake
-Hpath/to/cmakelists/folder
-Bpath/to/generated/ninja/project/debug/ABI
-DANDROID_ABI=ABI                               // For example, arm64-v8a
-DANDROID_PLATFORM=platform-version-string      // For example, android-16
-DANDROID_NDK=android-sdk/ndk/ndk-version
-DCMAKE_TOOLCHAIN_FILE=android-sdk/ndk/ndk-version/build/cmake/android.toolchain.cmake
-G Ninja

To polecenie wygeneruje projekt Ninja, który można wykonać, aby utworzyć biblioteki wykonywalne na Androida (pliki .so). Element CMAKE_TOOLCHAIN_FILE jest wymagany do korzystania z obsługi CMake (NDK). W przypadku CMake 3.21 lub nowszego można użyć wbudowanej obsługi NDK w CMake, ale należy użyć innej grupy zmiennych zgodnie z opisem w dokumentacji kompilacji krzyżowej na Androida tego programu.