在 Android Studio 中集成 Conan¶
在 使用 NDK 交叉构建到 Android 中,我们学习了如何使用 NDK 为 Android 构建一个包。在本示例中,我们将学习如何使用 Android Studio 进行此操作,以及如何在实际的 Android 应用程序中使用这些库。
创建新项目¶
首先,下载并安装 Android Studio IDE。
然后,选择 Native C++
从模板中创建一个 new project
。
在下一个向导窗口中,为您的应用程序选择一个名称,例如 MyConanApplication,您可以将“Minimum SDK”保留为建议值(在本例中为 21),但请记住该值,因为我们稍后将在 Conan profile 中的 os.api_level`
中使用它。
在“Build configuration language”中,您可以选择 Groovy DSL (build.gradle)
或 Kotlin DSL (build.gradle.kts)
,以便使用下面的 conanInstall 任务。
在下一个窗口中选择“C++ Standard”,再次记住您的选择,因为稍后我们应该在 profile 中的 compiler.cppstd
中使用相同的值。
在向导生成的项目中,我们有一个 cpp
文件夹和一个 native-lib.cpp
文件。我们将修改该文件以使用 zlib
并打印一条带有使用的 zlib
版本的消息。仅复制高亮显示的行,保留函数名很重要。
#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
的文件。
[requires]
zlib/1.2.12
[generators]
CMakeToolchain
CMakeDeps
[layout]
cmake_layout
build.gradle¶
我们将自动化调用 conan install
,然后再构建 Android 项目,以便准备好依赖项。打开 My_Conan_App.app
中的 build.gradle
文件(在 Android 项目视图的“Gradle Scripts”部分查找)。将 task conanInstall
的内容粘贴到 plugins
之后和 android
元素之前。
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 {
...
plugins {
...
}
tasks.register("conanInstall") {
val conanExecutable = "conan" // define the path to your conan installation
val buildDir = file("app/build")
buildDir.mkdirs()
val buildTypes = listOf("Debug", "Release")
val architectures = listOf("armv7", "armv8", "x86", "x86_64")
doLast {
buildTypes.forEach { buildType ->
architectures.forEach { arch ->
val cmd = "$conanExecutable install ../../src/main/cpp --profile android-studio " +
"-s build_type=$buildType -s arch=$arch --build missing " +
"-c tools.cmake.cmake_layout:build_folder_vars=['settings.arch']"
println(">> $cmd")
val proc = ProcessBuilder(cmd.split(" "))
.directory(buildDir)
.start()
val result = proc.inputStream.bufferedReader().readText()
val errors = proc.errorStream.bufferedReader().readText()
proc.waitFor()
if (proc.exitValue() != 0) {
throw Exception("Execution failed! Output: $result Error: $errors")
}
println(result)
if (errors.isNotBlank()) {
println("Errors: $errors")
}
}
}
}
}
tasks.named("preBuild").configure {
dependsOn("conanInstall")
}
android {
compileSdk 32
defaultConfig {
...
``conanInstall`` 任务会调用 ``conan install`` 来构建 Debug/Release 版本以及我们想要构建的每种架构,您可以根据需要调整这些值。
如果我们关注 ``conan install`` 任务,我们可以看到
我们传递了 ``--profile android``,因此我们需要创建 profile。转到 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-ndkinclude(default) [settings] os=Android os.api_level=21 compiler=clang compiler.version=12 compiler.libcxx=c++_static compiler.cppstd=14 [tool_requires] *: android-ndk/r26d您可能需要修改
``tools.android:ndk_path`` conf:Android Studio 提供的 NDK 的位置。如果您在 IDE 中打开 ``cpp/includes`` 文件夹,应该能够看到 NDK 的路径。
``compiler.version``:检查 NDK 文档或查找包含编译器可执行文件的 ``bin`` 文件夹,例如 ``x86_64-linux-android31-clang``。在 Macos 安装中,它位于 NDK 路径 + ``toolchains/llvm/prebuilt/darwin-x86_64/bin``。运行 ``./x86_64-linux-android31-clang --version`` 来检查正在运行的 ``clang`` 版本并调整 profile。
``compiler.libcxx``:支持的值为 ``c++_static`` 和 ``c++_shared``。
``compiler.cppstd``:C++ 标准版本,这应该是您在向导中选择的值。
``os.api_level``:使用您在向导中选择的相同值。
我们传递了 ``-c tools.cmake.cmake_layout:build_folder_vars=['settings.arch']``,这使得 Conan 为指定的 ``settings.arch`` 创建一个不同的文件夹,因此我们可以同时拥有所有配置。
为了让 Conan 工作,我们需要向 CMake 传递一个自定义工具链。我们可以在同一个文件中的 ``android/defaultConfig/externalNativeBuild/cmake`` 元素中添加一行来完成此操作。
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`` 的文件,该文件将负责根据指示 IDE 当前正在运行的构建配置的 ``ANDROID_ABI`` 变量来包含正确的工具链。
# 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`` 库。
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 码在虚拟设备或真实设备上运行应用程序,我们可以看到
一旦我们配置好项目,就可以非常容易地更改我们的依赖项并继续开发应用程序。例如,我们可以编辑 ``conanfile.txt`` 文件并将 ``zlib`` 更改为版本 ``1.12.2``。
[requires]
zlib/1.2.12
[generators]
CMakeToolchain
CMakeDeps
[layout]
cmake_layout
如果我们点击构建然后运行应用程序,我们将看到 zlib 依赖项已更新。