Configurar o CMake

Um script de compilação do CMake é um arquivo de texto simples que precisa ter o nome CMakeLists.txt e os comandos que o CMake usa para criar suas bibliotecas C/C++. Se as fontes nativas ainda não têm um script de compilação do CMake, é necessário criar e incluir um desses scripts nos comandos apropriados do CMake. Para saber como instalar o CMake, consulte Instalar e configurar o NDK e o CMake.

Esta seção cobre alguns comandos básicos que precisam ser incluídos no script de compilação para informar ao CMake quais origens precisam ser usadas na criação da biblioteca nativa. Para saber mais, leia a documentação oficial sobre os comandos do CMake.

Depois de configurar um novo script de compilação do CMake, você precisa configurar o Gradle para incluir seu projeto do CMake como uma dependência de build. Assim, o Gradle criará e empacotará sua biblioteca nativa junto ao APK do app.

Observação: se o projeto incluir o ndk-build, não vai ser necessário criar um script de build do CMake. Você pode simplesmente configurar o Gradle para incluir o projeto de biblioteca nativa existente fornecendo um caminho para seu arquivo Android.mk.

Criar um script de compilação do CMake

Para criar um arquivo de texto simples que possa ser usado como script de compilação do CMake, faça o seguinte:

  1. Abra o painel Project no lado esquerdo do ambiente de desenvolvimento integrado e selecione a visualização Project no menu suspenso.
  2. Clique com o botão direito do mouse no diretório raiz do your-module e selecione New > File.

    Observação: você pode criar o script de compilação no local que quiser. No entanto, ao configurá-lo, os caminhos para os arquivos de origem e bibliotecas nativos são relativos ao local do script de compilação.

  3. Insira "CMakeLists.txt" como nome do arquivo e clique em OK.

Agora é possível configurar o build script adicionando comandos do CMake. Para instruir o CMake a criar uma biblioteca nativa a partir do código-fonte nativo, adicione os comandos cmake_minimum_required() e add_library() ao script de compilação:

# 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 )

Dica: assim como você pode instruir o CMake a criar uma biblioteca nativa de arquivos de origem, use o comando add_executable() para instruir o CMake a criar um arquivo executável desses arquivos de origem. No entanto, criar arquivos executáveis a partir das suas fontes nativas é opcional, e criar bibliotecas nativas para serem incluídas no seu APK satisfaz a maioria dos requisitos de projeto.

Quando você adiciona um arquivo ou uma biblioteca de origem ao script de compilação do CMake usando add_library(), o Android Studio também mostra os arquivos principais associados na visualização Project após a sincronização do projeto. No entanto, para que o CMake localize os arquivos principais durante o tempo de compilação, será necessário adicionar o comando include_directories() ao script de compilação do CMake e especificar o caminho até os cabeçalhos:

add_library(...)

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

A convenção de nomenclatura usada pelo CMake para os arquivos da biblioteca é a seguinte:

liblibrary-name.so

Por exemplo, se você especificar “native-lib” como o nome da biblioteca compartilhada no script de compilação, o CMake criará um arquivo chamado libnative-lib.so. No entanto, para carregar essa biblioteca no código Java ou Kotlin, use o nome especificado no script de build do CMake:

Kotlin

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

Java

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

Observação: se você renomear ou remover uma biblioteca no script de build do CMake, será necessário limpar o projeto antes que o Gradle aplique as modificações ou remova do APK a versão antiga da biblioteca. Para limpar o projeto, selecione Build > Clean Project na barra de menus.

O Android Studio adiciona automaticamente os arquivos de origem e cabeçalhos ao grupo cpp no painel Project. Usando vários comandos add_library(), é possível definir outras bibliotecas para que o CMake use outros arquivos de origem para criação.

Adicionar APIs do NDK

O Android NDK oferece um conjunto de APIs e bibliotecas nativas que podem ser úteis. Você pode usar qualquer uma dessas APIs incluindo as bibliotecas do NDK no arquivo de script CMakeLists.txt do seu projeto.

Bibliotecas NDK pré-criadas já existem na plataforma Android. Portanto, você não precisa criá-las ou incluí-las no seu APK. Como as bibliotecas NDK já são uma parte do caminho de busca do CMake, você não precisa especificar a localização da biblioteca na sua instalação local do NDK. Você só precisa fornecer ao CMake o nome da biblioteca que você quer usar e vinculá-la à sua biblioteca nativa.

Adicione o comando find_library() (link em inglês) ao script de compilação do CMake para localizar uma biblioteca do NDK e armazenar o caminho dela como variável. Use essa variável como referência à biblioteca do NDK em outras partes do script de compilação. A amostra a seguir localiza a Biblioteca de Suporte de registro específica do Android e armazena o caminho dela em 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 )

Para que sua biblioteca nativa chame funções na biblioteca log, é necessário vinculá-las usando o comando target_link_libraries() (link em inglês) no script de compilação do 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} )

O NDK também contém algumas bibliotecas como código-fonte que você precisa criar e vincular à biblioteca nativa. Para compilar o código-fonte em uma biblioteca nativa, use o comando add_library() no script de compilação do CMake. Para informar um caminho à biblioteca do NDK local, você pode usar a variável de caminho ANDROID_NDK, definida automaticamente pelo Android Studio.

O comando a seguir instrui o CMake a criar android_native_app_glue.c, que gerencia eventos de ciclo de vida e entrada por toque de NativeActivity, em uma biblioteca estática e o vincula a 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} )

Adicionar outras bibliotecas pré-compiladas

A adição de uma biblioteca pré-compilada é semelhante à especificação de outra biblioteca nativa para criação pelo CMake. No entanto, como a biblioteca já foi criada, é necessário usar a sinalização IMPORTED (link em inglês) para informar ao CMake que você só quer importar a biblioteca para o projeto:

add_library( imported-lib
             SHARED
             IMPORTED )

Em seguida, é preciso especificar o caminho para a biblioteca usando o comando set_target_properties() (link em inglês), conforme mostrado abaixo.

Algumas bibliotecas fornecem pacotes separados para arquiteturas específicas de CPU ou Interfaces binária do aplicativo (ABIs, na sigla em inglês) e os organizam em diretórios diferentes. Essa abordagem ajuda as bibliotecas a aproveitar determinadas arquiteturas de CPU sem impedir que você use apenas as versões da biblioteca que quiser. Para adicionar várias versões de ABI de uma biblioteca ao script de compilação do CMake sem ter que codificar vários comandos para cada versão da biblioteca, use a variável de caminho ANDROID_ABI. Essa variável usa uma lista das ABIs padrão compatíveis com o NDK ou uma lista filtrada de ABIs configuradas manualmente para que o Gradle use. Exemplo:

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 )

Para que o CMake localize seus arquivos principais durante o tempo de compilação, é necessário usar o comando include_directories() e incluir o caminho nos arquivos principais:

include_directories( imported-lib/include/ )

Observação: se você quiser empacotar uma biblioteca pré-compilada que não seja uma dependência de tempo de compilação (por exemplo, ao adicionar uma biblioteca pré-compilada que seja uma dependência de imported-lib), não será necessário seguir as instruções abaixo para vincular a biblioteca.

Para vincular a biblioteca pré-compilada à sua própria biblioteca nativa, adicione-a ao comando target_link_libraries() no script de compilação do CMake:

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

Para empacotar a biblioteca pré-compilada no seu APK, é necessário configurar manualmente o Gradle com o bloco sourceSets para incluir o caminho para o arquivo .so. Depois de criar seu APK, você pode confirmar quais bibliotecas foram empacotadas pelo Gradle no APK usando o APK Analyzer.

Incluir outros projetos do CMake

Se você quiser criar vários projetos do CMake e incluir as saídas deles no seu projeto do Android, é possível usar um arquivo CMakeLists.txt como seu script de compilação do CMake de nível superior, ou seja, aquele que você vincula ao Gradle, e adicionar outros projetos do CMake como dependências desse script de compilação. O seguinte script de compilação do CMake de nível superior usa o comando add_subdirectory() para especificar outro arquivo CMakeLists.txt como uma dependência de build e, em seguida, faz a vinculação à saída dele da mesma forma que faria com qualquer outra biblioteca pré-compilada.

# 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 )

Chamar o CMake na linha de comando

Use o seguinte comando para chamar o CMake e gerar um projeto do Ninja fora do 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

Esse comando gerará o projeto Ninja que pode ser executado para criar bibliotecas executáveis do Android (arquivos .so). O CMAKE_TOOLCHAIN_FILE é necessário para usar o suporte do NDK ao CMake. No CMake 3.21 ou mais recentes, é possível usar o suporte integrado do NDK ao CMake, mas um grupo diferente de variáveis precisa ser usado conforme descrito na documentação sobre Compilação cruzada para Android (link em inglês) do CMake.