Configurar o CMake

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

Esta seção cobre alguns comandos básicos que precisam ser incluídos no build script 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 (link em inglês).

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

Observação: se o projeto incluir o ndk-build, não será necessário criar um build script 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 build script do CMake

Para criar um arquivo de texto simples que possa ser usado como build script 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 seu-módulo e selecione New > File.

    Observação: você pode criar o build script 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 build script.

  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 build script:

    # 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 a partir de arquivos de origem, use o comando add_executable() para instruir o CMake a criar um arquivo executável a partir 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 build script 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 a compilação, será necessário adicionar o comando include_directories() ao build script 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 build script, 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 build script do CMake:

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

Observação: se você renomear ou remover uma biblioteca no build script 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 compilaçã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é-compiladas já existem na plataforma Android. Portanto, você não precisa compilá-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 build script 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 build script. 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 build script 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 compilar e vincular à biblioteca nativa. Para compilar o código-fonte em uma biblioteca nativa, use o comando add_library() no build script 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 vinculá-lo 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 compilação pelo CMake. No entanto, como a biblioteca já está compilada, é 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(), conforme mostrado abaixo.

Algumas bibliotecas fornecem pacotes separados para arquiteturas específicas de CPU ou Interfaces binárias 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 build script 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 build, é 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 build (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 build script 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 compilar 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 compilar 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 build script do CMake de nível superior, ou seja, aquele que você vincula ao Gradle, e adicionar outros projetos do CMake como dependências desse build script. O seguinte build script 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 )