Định cấu hình CMake

Tập lệnh bản dựng CMake là một tệp văn bản thuần túy mà bạn phải đặt tên là CMakeLists.txt và bao gồm các lệnh mà CMake sử dụng để tạo thư viện C/C++ của bạn. Nếu các nguồn gốc chưa có tập lệnh bản dựng CMake, bạn cần phải tự tạo một tập lệnh và thêm vào các lệnh CMake thích hợp. Để tìm hiểu cách cài đặt CMake, hãy xem phần Cài đặt và định cấu hình NDK và CMake.

Phần này trình bày một số lệnh cơ bản mà bạn nên đưa vào tập lệnh bản dựng để CMake biết cần sử dụng nguồn nào khi tạo thư viện gốc. Để tìm hiểu thêm, hãy đọc tài liệu chính thức về các lệnh CMake.

Sau khi định cấu hình tập lệnh bản dựng CMake mới, bạn cần định cấu hình Gradle để đưa dự án CMake vào dưới dạng phần phụ thuộc của bản dựng. Từ đó, Gradle sẽ tạo và đóng gói thư viện gốc của bạn với tệp APK của ứng dụng.

Lưu ý: Nếu dự án của bạn dùng ndk-build, bạn không cần tạo tập lệnh bản dựng CMake. Bạn chỉ cần định cấu hình Gradle để thêm dự án thư viện gốc hiện có bằng cách cung cấp đường dẫn đến tệp Android.mk của bạn.

Tạo tập lệnh bản dựng CMake

Để tạo một tệp văn bản thuần túy mà bạn có thể dùng làm tập lệnh bản dựng CMake, hãy thực hiện như sau:

  1. Mở ngăn Project (Dự án) từ bên trái của IDE và chọn chế độ xem Project (Dự án) từ trình đơn thả xuống.
  2. Nhấp chuột phải vào thư mục gốc của your-module rồi chọn New (Mới) > File (Tệp).

    Lưu ý: Bạn có thể tạo tập lệnh bản dựng ở bất kỳ vị trí nào mà bạn muốn. Tuy nhiên, khi định cấu hình tập lệnh bản dựng, đường dẫn đến các tệp và thư viện nguồn gốc sẽ tương ứng với vị trí của tập lệnh bản dựng.

  3. Nhập "CMakeLists.txt" làm tên tệp và nhấp vào OK.

Bây giờ bạn có thể định cấu hình tập lệnh bản dựng bằng cách thêm các lệnh CMake. Để hướng dẫn CMake tạo thư viện gốc từ mã nguồn gốc, hãy thêm lệnh cmake_minimum_required()add_library() vào tập lệnh bản dựng:

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

Mẹo: Tương tự như cách yêu cầu CMake tạo thư viện gốc từ tệp nguồn, bạn có thể dùng lệnh add_executable() để yêu cầu CMake tạo tệp thực thi từ những tệp nguồn đó. Tuy nhiên, bạn không bắt buộc phải tạo tệp thực thi từ nguồn gốc. Và việc xây dựng các thư viện gốc để đóng gói vào tệp APK đáp ứng hầu hết các yêu cầu của dự án.

Khi bạn thêm một tệp nguồn hoặc thư viện vào tập lệnh bản dựng CMake của mình bằng add_library(), Android Studio cũng sẽ hiển thị các tệp tiêu đề liên kết trong chế độ xem Project (Dự án) sau khi bạn đồng bộ hóa dự án. Tuy nhiên, để CMake định vị các tệp tiêu đề trong thời gian biên dịch, bạn cần thêm lệnh include_directories() vào tập lệnh bản dựng CMake và chỉ định đường dẫn đến tiêu đề:

add_library(...)

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

Quy ước mà CMake dùng để đặt tên cho tệp của thư viện như sau:

liblibrary-name.so

Ví dụ: nếu bạn chỉ định "native-lib" làm tên của thư viện chia sẻ trong tập lệnh bản dựng, thì CMake sẽ tạo một tệp có tên là libnative-lib.so. Tuy nhiên, khi tải thư viện này trong mã Java hoặc Kotlin, hãy dùng tên mà bạn đã chỉ định trong tập lệnh bản dựng CMake:

Kotlin

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

Java

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

Lưu ý: Nếu đổi tên hoặc xoá thư viện trong tập lệnh bản dựng CMake, bạn cần dọn sạch dự án trước khi Gradle áp dụng thay đổi hoặc xoá phiên bản thư viện cũ hơn khỏi tệp APK của bạn. Để dọn sạch dự án, hãy chọn Build (Bản dựng) > Clean Project (Dọn sạch dự án) trên thanh trình đơn.

Android Studio sẽ tự động thêm các tệp nguồn và tiêu đề vào nhóm cpp trong ngăn Project (Dự án). Khi sử dụng nhiều lệnh add_library(), bạn có thể xác định thêm các thư viện bổ sung để CMake tạo từ các tệp nguồn khác.

Thêm API NDK

Android NDK cung cấp một bộ API và thư viện gốc mà bạn có thể sẽ thấy hữu ích. Bạn có thể sử dụng bất kỳ API nào trong số này bằng cách đưa thư viện NDK vào tập lệnh CMakeLists.txt của dự án.

Các thư viện NDK tạo sẵn đã có trên nền tảng Android, vì vậy bạn không cần tạo hoặc đóng gói các thư viện này vào tệp APK. Vì các thư viện NDK đã có trong đường dẫn tìm kiếm của CMake, nên bạn thậm chí không cần chỉ định vị trí của thư viện trong quá trình cài đặt NDK cục bộ. Bạn chỉ cần cung cấp cho CMake tên của thư viện mà bạn muốn sử dụng và liên kết nó với thư viện gốc của riêng mình.

Thêm lệnh find_library() vào tập lệnh bản dựng CMake để xác định vị trí thư viện NDK và lưu trữ đường dẫn của thư viện đó làm một biến. Bạn sử dụng biến này để tham chiếu đến thư viện NDK trong các phần khác của tập lệnh bản dựng. Mẫu sau đây xác định vị trí của thư viện hỗ trợ nhật ký riêng cho Android và lưu trữ đường dẫn của thư viện đó trong 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 )

Để thư viện gốc của bạn gọi các hàm trong thư viện log, bạn cần liên kết các thư viện bằng lệnh target_link_libraries() trong tập lệnh bản dựng 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} )

NDK cũng tích hợp một số thư viện dưới dạng mã nguồn mà bạn cần tạo và liên kết đến thư viện gốc. Bạn có thể biên dịch mã nguồn thành thư viện gốc bằng cách sử dụng lệnh add_library() trong tập lệnh bản dựng CMake. Để cung cấp đường dẫn đến thư viện NDK cục bộ, bạn có thể sử dụng biến đường dẫn ANDROID_NDK mà Android Studio sẽ tự động xác định cho bạn.

Lệnh sau đây yêu cầu CMake tạo android_native_app_glue.c (quản lý các sự kiện trong vòng đời của NativeActivity và tính năng nhập bằng cách nhấn) vào một thư viện tĩnh và liên kết tài khoản đó với 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} )

Thêm các thư viện tạo sẵn khác

Cách thêm thư viện tạo sẵn cũng tương tự như cách chỉ định thư viện gốc khác để CMake tạo. Tuy nhiên, vì thư viện này đã được tạo, nên bạn cần phải sử dụng cờ IMPORTED để thông báo cho CMake biết rằng bạn chỉ muốn nhập thư viện vào dự án của mình:

add_library( imported-lib
             SHARED
             IMPORTED )

Lúc này, bạn cần chỉ định đường dẫn đến thư viện bằng lệnh set_target_properties() như bên dưới.

Một số thư viện cung cấp các gói riêng cho các cấu trúc CPU cụ thể – hay còn gọi là Giao diện nhị phân của ứng dụng (ABI). Đồng thời, các thư viện này còn sắp xếp các gói đó thành các thư mục riêng. Phương pháp này giúp thư viện tận dụng được một số cấu trúc CPU nhất định, đồng thời chỉ cho phép bạn sử dụng các phiên bản thư viện bạn muốn. Để thêm nhiều phiên bản ABI của thư viện vào tập lệnh bản dựng CMake mà không cần phải viết nhiều lệnh cho từng phiên bản thư viện, bạn có thể sử dụng biến đường dẫn ANDROID_ABI. Biến này sử dụng danh sách ABI mặc định mà NDK hỗ trợ hoặc danh sách ABI đã lọc mà bạn định cấu hình Gradle theo cách thủ công để dùng. Ví dụ:

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 )

Để CMake định vị các tệp tiêu đề trong thời gian biên dịch, bạn cần sử dụng lệnh include_directories() và thêm đường dẫn đến tệp tiêu đề của mình:

include_directories( imported-lib/include/ )

Lưu ý: Nếu bạn muốn đóng gói một thư viện tạo sẵn không phải là phần phụ thuộc tại thời gian xây dựng, (ví dụ: khi thêm một thư viện tạo sẵn là phần phụ thuộc của imported-lib), bạn không cần thực hiện hướng dẫn sau để liên kết thư viện.

Để liên kết thư viện tạo sẵn với thư viện gốc của riêng bạn, hãy thêm thư viện đó vào lệnh target_link_libraries() trong tập lệnh bản dựng CMake:

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

Để đóng gói thư viện tạo sẵn vào APK, bạn cần định cấu hình Gradle theo cách thủ công với khối sourceSets để tích hợp đường dẫn đến tệp .so của bạn. Sau khi tạo tệp APK, bạn có thể dùng Công cụ phân tích APK để xác minh Gradle đóng gói thư viện nào trong tệp APK của mình.

Bao gồm các dự án CMake khác

Nếu muốn tạo nhiều dự án CMake và đưa đầu ra của các dự án đó vào dự án Android của mình, bạn có thể sử dụng một tệp CMakeLists.txt làm tập lệnh bản dựng CMake cấp cao nhất (đây là tập lệnh mà bạn liên kết đến Gradle) và thêm các dự án CMake bổ sung làm phần phụ thuộc của tập lệnh bản dựng đó. Tập lệnh bản dựng CMake cấp cao nhất sau đây sử dụng lệnh add_subdirectory() để chỉ định một tệp CMakeLists.txt khác làm tệp phụ thuộc cho bản dựng, sau đó liên kết với dữ liệu đầu ra của bản dựng đó như với bất kỳ thư viện tạo sẵn nào khác.

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

Gọi CMake từ dòng lệnh

Sử dụng lệnh sau để gọi CMake nhằm tạo một dự án Ninja bên ngoài 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

Lệnh này sẽ tạo dự án Ninja có thể được thực thi để tạo thư viện thực thi Android (các tệp .so). CMAKE_TOOLCHAIN_FILE là bắt buộc để sử dụng hỗ trợ CMake của NDK. Đối với CMake 3.21 trở lên, bạn có thể sử dụng tính năng hỗ trợ NDK tích hợp của CMake để thay thế. Nhưng bạn phải sử dụng một nhóm biến khác như đã mô tả trong tài liệu Biên dịch chéo cho Android của CMake.