您的位置:首页 > 移动开发 > Android开发

【Android小品】OpenCV配置 之一 CMake详解(基于最新平台AS 2.2)

2016-12-19 09:28 363 查看
接下来几篇文章将讲述在Android Studio上配置openCv的过程

一、背景知识

什么是CMake?

  CMake是一个跨平台的编译辅助工具(构建工具),可以用简单的语句(CmakeList.txt)来描述所有平台的安装(编译过程)

为什么需要CMake这样的构建工具?

  但是当你的程序包含很多个源文件时,如果直接在控制台使用编译器命令(gcc等)逐个去编译时,会发现十分混乱而且工作量超大。编译工具包可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式—通过调用makefile文件(里面就包含了调用编译器的命令)来进行编译和链接的。而这个makefile文件是通过CmakeList.txt、 Android.mk文件生成的。CmakeList.txt、 Android.mk就是你要写的东西。具体就是指定一些目录啊、版本啊等等,具体的活儿都不需要你来做了。

  做个不太贴切的类比:CmakeList.txt、 Android.mk的作用就好像是build.gradle。

  CMake是专门用来辅助C/C++等原生代码的编译。它们会根据CmakeList.txt、 Android.mk操作编译器把.cpp、.h、.hpp这样的源文件编译成Android能看得懂的.so文件。

.so文件是什么?

  .so文件是Linux中的动态链接库文件,等价于Windows下的.dll。

  所谓动态链接,就是说函数的函数体是在运行时确定的。函数的可执行代码存放在.dll(.so) 文件中,.dll(.so)包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。

  使用动态链接库可以更为容易地将更新应用于各个模块,而不会影响该程序的其他部分。例如,一个大型网络游戏,如果把整个数百MB甚至数GB的游戏的代码都放在一个应用程序里,日后的修改工作将会十分费时,更新起来也不方便,而如果把不同功能的代码分别放在数个动态链接库中,无需重新生成或安装整个程序就可以应用更新。

二、综述

  想要正确的配置C/C++库首先要有一个大致上的概念,知道为什么这么做才能够真正理解并记忆下来。

  我们在Android中使用原生代码都是根据.so文件的,而CMake是为了操作编译器把.c文件编译成为.so。换句话说其实如果你直接有.so,就不需要使用源代码了。

  一般我们首先会在JAVA代码中使用native方法,然后通过javah命令去生成头文件。然后在C代码中包含这个头文件,并对函数体提供定义。如果我们简单的需要C去运算一些密码等等这就够了。但是如果我们需要去调用类似opencv这样的C代码库,则就还需要一些步骤。因为系统是不知道你的C库在哪里,有哪些函数的,所以需要进行配置。

  使用第三方库需要三方面准备:

  1.将第三方库编译好的.so文件放入工程的jni库目录(如果没有要配置gradle)

  2.添加每一个.so为单独的library(动态链接库,告诉系统如果需要函数,就执行我里面的函数体)

  3.指定第三方C库的头文件目录(.so是函数体,头文件用来告诉编译器函数签名)

  4.链接到你自己的原生库。(系统知道了第三方库的函数签名和函数定义不行,还要告诉系统你的库想要用哪些东西,所以需要链接)

这些和VS里面使用C/C++库非常类似

三、CMake在Android Studio使用

在Android Studio 2.2以上的版本中,你就可以直接使用CMake编译源代码文件,Android Studio会使用Gradle自动把CMake生成的文件打包到你的apk中(其实编译步骤也是自动的,Android Studio能够自动的读取CMakeList.txt然后调用CMake进行编译)。

  Android Studio2.2以上版本构建原生库的默认工具是 CMake。但为了兼容性Android Studio 仍支持 ndk-build(后文会提到)。如果在创建新的原生库,则应使用 CMake。

  如果执意使用ndk-build,那么需要在build.properties里面加上这一行

android.useDeprecatedNdk = true


下载 NDK 和构建工具

Android 原生开发工具包 (NDK):这套工具集允许你为 Android 使用 C 和 C++ 代码,并提供众多平台库,你可以管理原生 Activity 和访问物理设备组件,例如传感器和触摸输入。里面包含ndk-build和一些库,虽然说ndk-build被cmake代替了,但是其他库还是要的,所以这个还需要装

CMake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果只计划使用 ndk-build,则不需要此组件。

LLDB:一种调试程序,Android Studio 使用它来调试原生代码。

可以使用 SDK 管理器(目前Android Studio已经内置)安装这些组件:

在打开的项目中,从菜单栏选择 Tools > Android > SDK Manager。

点击 SDK Tools 标签。

选中 LLDB、CMake 和 NDK 旁的复选框,如图 1 所示。



图 1.从 SDK 管理器中安装 LLDB、CMake 和 NDK。

点击 Apply,然后在弹出式对话框中点击 OK。

安装完成后,点击 Finish,然后点击 OK。

创建支持 C/C++ 的新项目

创建支持原生代码的项目与创建任何其他 Android Studio 项目类似,不过还需额外几个步骤:

在向导的 Configure your new project 部分,选中 Include C++ Support 复选框。

点击 Next。

正常填写所有其他字段并完成向导接下来的几个部分。

在向导的 Customize C++ Support 部分,可以使用下列选项自定义项目:

C++ Standard:使用下拉列表选择使用的 C++ 标准。选择 Toolchain Default 会使用默认的 CMake 设置。

Exceptions Support:如果希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

Runtime Type Information Support:如果希望支持 RTTI(运行时类型信息),请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

点击 Finish。

  在 Android Studio 完成新项目的创建后,请从 IDE 左侧打开 Project 窗格并选择 Android 视图。如图 2 中所示,Android Studio 将添加 cpp 和 External Build Files 组:



图 2.原生源文件。

在 cpp 组中,你可以找到属于项目的所有原生源文件、标头和预构建库。对于新项目,Android Studio 会创建一个示例 C++ 源文件 native-lib.cpp,并将其置于应用模块的 src/main/cpp/ 目录中。本示例代码提供了一个简单的 C++ 函数 stringFromJNI(),此函数可以返回字符串“Hello from C++”。

在 External Build Files 组中,你可以找到 CMake 或 ndk-build 的构建脚本。与 build.gradle 文件指示 Gradle 如何构建应用一样,CMake 和 ndk-build 需要一个构建脚本来了解如何构建原生库。对于新项目,Android Studio 会创建一个 CMake 构建脚本 CMakeLists.txt,并将其置于模块的根目录中。(后文会介绍如何编写 CMakeLists.txt 和 Android.mk)

构建和运行示例应用

点击 Run 从菜单栏运行应用 后,Android Studio 将在 Android 设备或者模拟器上构建并启动一个显示文字“Hello from C++”的应用。下面的概览介绍了构建和运行示例应用时会发生的事件:

Gradle 调用外部构建脚本 CMakeLists.txt。

CMake 按照构建脚本中的命令将 C++ 源文件 native-lib.cpp 编译到共享的对象库中,并命名为 libnative-lib.so,Gradle 随后会将其封装到 APK 中。

运行时,应用的 MainActivity 会使用 System.loadLibrary() 加载原生库。现在,应用可以使用库的原生函数 stringFromJNI()。

MainActivity.onCreate() 调用 stringFromJNI(),这将返回“Hello from C++”并使用这些文字更新 TextView。

注:使用原生代码,Instant Run就不能用了,Android Studio 会自动停用此功能。

如果想要验证 Gradle 是否已将原生库封装到 APK 中,可以使用 APK 分析器:

选择 Build > Analyze APK。

从 app/build/outputs/apk/ 目录中选择 APK 并点击 OK。

如图 3 中所示,可以在 APK 分析器窗口的 lib/……/ 下看到 libnative-lib.so。



图 3.使用 APK 分析器定位原生库。

向现有项目添加 C/C++ 代码

如果想向现有项目添加原生代码,请执行以下步骤:

创建新的原生源文件并将其添加到 Android Studio 项目中。

如果已经拥有原生代码或想要导入预构建的原生库,则可以跳过此步骤。

创建 CMake 构建脚本,将原生源代码构建到库中。如果导入和关联预构建库或平台库,你也需要此构建脚本。

如果现有原生库已经拥有 CMakeLists.txt 构建脚本或者使用 ndk-build 并包含 Android.mk 构建脚本,则可以跳过此步骤。

提供一个指向 CMake 或 ndk-build 脚本文件的路径,将 Gradle 关联到原生库。Gradle 使用构建脚本将源代码导入 Android Studio 项目并将原生库(SO 文件)封装到 APK 中。

配置完项目后,可以使用 JNI 框架从 Java 代码中访问原生函数。要构建和运行应用,只需点击 Run 从菜单栏运行应用。Gradle 会以依赖项的形式添加外部原生构建流程,用于编译、构建原生库并将其随 APK 一起封装。

创建新的原生源文件

要在应用模块的主源代码集中创建一个包含新建原生源文件的 cpp/ 目录,请按以下步骤操作:

从 IDE 的左侧打开 Project 窗格并从下拉菜单中选择 Project 视图。

导航到moudle > src,右键点击 main 目录,然后选择 New > Directory。

为目录输入一个名称(例如 cpp)并点击 OK。

右键点击刚刚创建的目录,然后选择 New > C/C++ Source File。

为源文件输入一个名称,例如 native-lib。

从 Type 下拉菜单中,为源文件选择文件扩展名,例如 .cpp。

点击 Edit File Types ,可以向下拉菜单中添加其他文件类型,例如 .cxx 或 .hxx。在弹出的 C/C++ 对话框中,从 Source Extension 和 Header Extension 下拉菜单中选择另一个文件扩展名,然后点击 OK。

如果还希望创建一个标头文件,请选中 Create an associated header 复选框。

点击 OK。

创建 CMake 构建脚本

如果原生源文件还没有 CMake 构建脚本,则你需要自行创建一个并包含适当的 CMake 命令。CMake 构建脚本是一个纯文本文件,你必须将其命名为 CMakeLists.txt。本部分介绍了包含到构建脚本中的一些基本命令,用于在创建原生库时指示 CMake 应使用哪些源文件。

注:如果项目使用 ndk-build,则不需要创建 CMake 构建脚本。提供一个指向 Android.mk 文件的路径,将 Gradle 关联到原生库。

要创建一个可以用作 CMake 构建脚本的纯文本文件,请按以下步骤操作:

从 IDE 的左侧打开 Project 窗格并从下拉菜单中选择 Project 视图。

右键点击moudle的根目录并选择 New > File。

输入“CMakeLists.txt”作为文件名并点击 OK。

现在,你可以添加 CMake 命令,对构建脚本进行配置。要指示 CMake 从原生源代码创建一个原生库,请将 cmake_minimum_required() 和 add_library() 命令添加到构建脚本中:

# 用于指定项目需要的cmake最低版本.
# cmake通过这个选项来确定能够使用那些特性
cmake_minimum_required(VERSION 3.4.1)

# 指派原生库名称、模式以及源码目录

add_library( # 库名称
native-lib

# 模式
SHARED

# 源码目录
src/main/cpp/native-lib.cpp )


使用 add_library() 向 CMake 构建脚本添加源文件或库时,还需要将 include_directories() 命令添加到 CMake 构建脚本中并指定头文件的路径:

提示:就像VS要指定的include一样,在这里指定了IDE和编译器才能找到#include<…>尖括号里面的东西

# 指定头文件文件夹
include_directories(src/main/cpp/include/)


CMake 使用以下规范来为库文件命名:

lib+库名称+.so

例如,如果在构建脚本中指定“native-lib”作为共享库的名称,CMake 将创建一个名称为 libnative-lib.so 的文件。不过,在 Java 代码中加载此库时,请使用在 CMake 构建脚本中指定的名称:

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


Android Studio 会自动将源文件和标头添加到 Project 窗格的 cpp 组中。使用多个 add_library() 命令,可以为 CMake 定义要从其他源文件构建的更多库。

添加 NDK API

Android NDK 提供了一套实用的原生 API 和库。通过将 NDK 库包含到项目的 CMakeLists.txt 脚本文件中,可以使用这些 API 中的任意一种。

预构建的 NDK 库已经存在于 Android 平台上,因此,无需再构建或将其封装到 APK 中。由于 NDK 库已经是 CMake 搜索路径的一部分,甚至不需要在本地 NDK 安装中指定库的位置 - 只需要向 CMake 提供希望使用的库的名称,并将其关联到自己的原生库。

将 find_library() 命令添加到 CMake 构建脚本中以定位 NDK 库,并将其路径存储为一个变量。可以使用此变量在构建脚本的其他部分引用 NDK 库。以下示例可以定位 Android 特定的日志支持库并将其路径存储在 log-lib 中:

find_library( # ndklib的位置
log-lib
# ndklib的名称
log )


为了确保原生库可以在 log 库中调用函数,需要使用 CMake 构建脚本中的 target_link_libraries() 命令关联库:

# 将原生库和你的库连接起来(这里可以一行链接多个库)
target_link_libraries( # 目标库
native-lib

# 要链接的库
${log-lib} )


NDK 还以源代码的形式包含一些库,在构建和关联到原生库时需要使用这些代码。可以使用 CMake 构建脚本中的 add_library() 命令,将源代码编译到原生库中。要提供本地 NDK 库的路径,可以使用 ANDROID_NDK 路径变量,Android Studio 会自动定义此变量。

以下命令可以指示 CMake 构建 android_native_app_glue.c,后者会将 NativeActivity 生命周期事件和触摸输入置于静态库中并将静态库关联到 native-lib:

add_library( app-glue
STATIC
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )

# 链接库
target_link_libraries( native-lib app-glue ${log-lib} )


添加其他预构建库(OpenCv就是预构建库)

添加预构建库与为 CMake 指定要构建的另一个原生库类似。不过,由于库已经预先构建,需要使用 IMPORTED 标志告知 CMake 你只希望将库导入到项目中:

#很重要:我发现IMPORTED的set_target_properties()中只能写一个.so文件的目录,如果有多个.so则需要每个都建立一个library,然后最后一起链接
add_library( imported-lib
SHARED
IMPORTED )


然后,你需要使用 set_target_properties() 命令指定库的路径,如下所示。

某些库为特定的 CPU 架构(或应用二进制接口 (ABI))提供了单独的软件包,(这里ABI指的是 mip64等等这些so库的文件夹),并将其组织到单独的目录中。此方法既有助于库充分利用特定的 CPU 架构,又能让你仅使用所需的库版本。要向 CMake 构建脚本中添加库的多个 ABI 版本,而不必为库的每个版本编写多个命令,可以使用 ANDROID_ABI 路径变量。此变量使用 NDK 支持的一组默认 ABI。例如:

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 可以在编写代码时#include头文件,需要使用 include_directories() 命令,并包含标头文件的路径:

include_directories( imported-lib/include/ )


然后将构件库链接到自己的原生库,以便在自己写的C代码中使用这些C/C++库,请将其添加到 CMake 构建脚本的 target_link_libraries() 命令中:

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


使用Android Studio将 Gradle 关联到原生库

可以使用 Android Studio UI 将 Gradle 关联到外部 CMake 或 ndk-build 项目:

从 IDE 左侧打开 Project 窗格并选择 Android 视图。

右键点击想要关联到原生库的模块(例如 app 模块),并从菜单中选择 Link C++ Project with Gradle。你应看到一个如图 4 所示的对话框。

从下拉菜单中,选择 CMake 或 ndk-build。

如果选择 CMake,请使用 Project Path 旁的字段为外部 CMake 项目指定 CMakeLists.txt 脚本文件。

如果选择 ndk-build,请使用 Project Path 旁的字段为外部 ndk-build 项目指定 Android.mk 脚本文件。如果 Application.mk 文件与 Android.mk 文件位于相同目录下,Android Studio 也会包含此文件。



图 4.使用 Android Studio 对话框关联外部 C++ 项目。

点击 OK。

如果要手动配置 Gradle 以关联到原生库,你需要将 externalNativeBuild {} 块添加到模块级 build.gradle 文件中,并使用 cmake {} 或 ndkBuild {} 对其进行配置:

android {
...
defaultConfig {...}
buildTypes {...}
externalNativeBuild {
cmake{
path "CMakeLists.txt"
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息