在 Android Studio 中集成 Conan

使用 NDK 交叉编译到 Android 中,我们学习了如何使用 NDK 为 Android 构建软件包。在本示例中,我们将学习如何在 Android Studio 中执行此操作,以及如何在真实的 Android 应用程序中使用库。

创建新项目

首先,下载并安装 Android Studio IDE

然后,从模板中选择 Native C++ 创建一个 新项目

在下一个向导窗口中,为您的应用程序选择一个名称,例如 MyConanApplication,您可以将“最低 SDK”保留为建议值(在我们的例子中为 21),但请记住该值,因为我们稍后将在 Conan 配置文件中的 os.api_level` 中使用它。

在“构建配置语言”中,您可以在 Groovy DSL (build.gradle)Kotlin DSL (build.gradle.kts) 之间进行选择,以便使用下面的 conanInstall 任务。

在下一个窗口中选择“C++ 标准”,同样,请记住您的选择,因为稍后我们应在配置文件中的 compiler.cppstd 中使用相同的值。

在向导生成的项目中,我们有一个 cpp 文件夹,其中包含 native-lib.cpp。我们将修改该文件以使用 zlib 并打印一条带有使用的 zlib 版本的消息。仅复制突出显示的代码行,保持函数名非常重要。

native-lib.cpp
 #include <jni.h>
 #include <string>
 #include "zlib.h"

 extern "C" JNIEXPORT jstring JNICALL
 Java_com_example_myconanapp_MainActivity_stringFromJNI(
         JNIEnv* env,
         jobject /* this */) {
     std::string hello = "Hello from C++, zlib version: ";
     hello.append(zlibVersion());
     return env->NewStringUTF(hello.c_str());
 }

现在,我们将学习如何引入对 zlib 库的要求,以及如何准备我们的项目。

使用 Conan 引入依赖项

conanfile.txt

我们需要使用 Conan 提供 zlib 包。在 cpp 文件夹中创建一个 conanfile.txt 文件

conanfile.txt
[requires]
zlib/1.2.12

[generators]
CMakeToolchain
CMakeDeps

[layout]
cmake_layout

build.gradle

我们将在构建 Android 项目之前自动调用 conan install,以便准备好所需的依赖项。打开 My_Conan_App.app 中的 build.gradle 文件(在 Android 项目视图的 Gradle Scripts 部分中查找)。将 task conanInstall 内容粘贴到 plugins 之后和 android 元素之前

build.gradle
plugins {
 ...
}

task conanInstall {
    def conanExecutable = "conan" // define the path to your conan installation
    def buildDir = new File("app/build")
    buildDir.mkdirs()
    ["Debug", "Release"].each { String build_type ->
        ["armv7", "armv8", "x86", "x86_64"].each { String arch ->
            def cmd = conanExecutable + " install " +
                      "../src/main/cpp --profile android -s build_type="+ build_type +" -s arch=" + arch +
                      " --build missing -c tools.cmake.cmake_layout:build_folder_vars=['settings.arch']"
            print(">> ${cmd} \n")

            def sout = new StringBuilder(), serr = new StringBuilder()
            def proc = cmd.execute(null, buildDir)
            proc.consumeProcessOutput(sout, serr)
            proc.waitFor()
            println "$sout $serr"
            if (proc.exitValue() != 0) {
                throw new Exception("out> $sout err> $serr" + "\nCommand: ${cmd}")
            }
        }
    }
}

android {
    compileSdk 32

    defaultConfig {

...

conanInstall 任务正在为 Debug/Release 以及我们要构建的每个架构调用 conan install,您可以调整这些值以满足您的需求。

如果我们专注于 conan install 任务,我们可以看到

  1. 我们正在传递 --profile android,因此我们需要创建配置文件。转到 conan 配置主目录中的 profiles 文件夹(运行 conan config home 来检查),创建一个名为 android 的文件,其中包含以下内容

    include(default)
    
    [settings]
    os=Android
    os.api_level=21
    compiler=clang
    compiler.version=12
    compiler.libcxx=c++_static
    compiler.cppstd=14
    
    [conf]
    tools.android:ndk_path=/opt/homebrew/share/android-ndk
    

    您可能需要修改

    • tools.android:ndk_path conf:Android Studio 提供的 NDK 的位置。如果您在 IDE 中打开 cpp/includes 文件夹,应该能够看到 NDK 的路径。

    • compiler.version:检查 NDK 文档或找到一个包含编译器可执行文件(如 x86_64-linux-android31-clang)的 bin 文件夹。在 Macos 安装中,它位于 NDK 路径 + toolchains/llvm/prebuilt/darwin-x86_64/bin 中。运行 ./x86_64-linux-android31-clang --version 以检查正在运行的 clang 版本并调整配置文件。

    • compiler.libcxx:支持的值为 c++_staticc++_shared

    • compiler.cppstd:C++ 标准版本,这应该是您在向导中选择的值。

    • os.api_level:使用您在向导中选择的相同值。

  2. 我们正在传递 -c tools.cmake.cmake_layout:build_folder_vars=['settings.arch'],因此,Conan 将为指定的 settings.arch 创建一个不同的文件夹,以便我们可以同时拥有所有可用的配置。

为了使 Conan 工作,我们需要向 CMake 传递一个自定义工具链。我们可以在同一个文件的 android/defaultConfig/externalNativeBuild/cmake 元素中引入一行来完成此操作

build.gradle
android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.myconanapp"
        minSdk 21
        targetSdk 21
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags '-v'
                arguments("-DCMAKE_TOOLCHAIN_FILE=conan_android_toolchain.cmake")
            }
        }

conan_android_toolchain.cmake

cpp 文件夹中创建一个名为 conan_android_toolchain.cmake 的文件,该文件将负责根据 ANDROID_ABI 变量(指示 IDE 当前正在运行的构建配置)包含正确的工具链

conan_android_toolchain.cmake
 # During multiple stages of CMake configuration, the toolchain file is processed and command-line
 # variables may not be always available. The script exits prematurely if essential variables are absent.

 if ( NOT ANDROID_ABI OR NOT CMAKE_BUILD_TYPE )
     return()
 endif()
 if(${ANDROID_ABI} STREQUAL "x86_64")
     include("${CMAKE_CURRENT_LIST_DIR}/build/x86_64/${CMAKE_BUILD_TYPE}/generators/conan_toolchain.cmake")
 elseif(${ANDROID_ABI} STREQUAL "x86")
     include("${CMAKE_CURRENT_LIST_DIR}/build/x86/${CMAKE_BUILD_TYPE}/generators/conan_toolchain.cmake")
 elseif(${ANDROID_ABI} STREQUAL "arm64-v8a")
     include("${CMAKE_CURRENT_LIST_DIR}/build/armv8/${CMAKE_BUILD_TYPE}/generators/conan_toolchain.cmake")
 elseif(${ANDROID_ABI} STREQUAL "armeabi-v7a")
     include("${CMAKE_CURRENT_LIST_DIR}/build/armv7/${CMAKE_BUILD_TYPE}/generators/conan_toolchain.cmake")
 else()
     message(FATAL "Not supported configuration")
 endif()

CMakeLists.txt

最后,我们需要修改 CMakeLists.txt 以链接到 zlib

CMakeLists.txt
cmake_minimum_required(VERSION 3.18.1)
project("myconanapp")
add_library(myconanapp SHARED native-lib.cpp)

find_library(log-lib log)

find_package(ZLIB CONFIG)

target_link_libraries(myconanapp ${log-lib} ZLIB::ZLIB)

构建应用程序

如果我们构建我们的项目,我们可以看到 conan install 被多次调用,构建了 zlib 的不同配置。

然后,如果我们在虚拟设备或真实设备上运行该应用程序,并通过 QR 码将其配对,我们可以看到

Android application showing the zlib 1.2.11

配置好我们的项目后,很容易更改我们的依赖项并继续开发应用程序。例如,我们可以编辑 conanfile.txt 文件并将 zlib 更改为 1.12.2 版本

[requires]
zlib/1.2.12

[generators]
CMakeToolchain
CMakeDeps

[layout]
cmake_layout

如果我们单击构建,然后运行该应用程序,我们将看到 zlib 依赖项已更新

Android application showing the zlib 1.2.12