Android Studio添加原生库并自动构建
[时间:2017-09] [状态:Open]
[关键词:Android,Android Studio,gradle,native,c,c++,cmake,原生开发,ndk-build]
0 引言
最近在工作中遇到了升级Android Studio 2.3.3稳定版之后,无法编译jar包的问题。之后寻找AS文档-探索 Android Studio发现。可以通过AS创建和编译jar包,顺便看到支持原生开发,可以直接在AS中调试c/c++代码,这是非常不错的功能。终于可以摆脱纯打日志的开发环境了。
本系列文章也就因此而出现。我希望阅读文本文之后,大家能够基本了解如何使用新版的Android Studio开发原生引用。
这里说明一点,本文是参考谷歌的Android Studio官网来的,也可以认为是一种翻译版。可以访问的话你可以直接查看对应内容。
这是第二篇:现有AS项目中添加原生库。
1 环境准备
请参考本系列的第一篇:原生库示例创建及验证中的要求,按照和更新Android Studio。本文需要事先创建一个AS工程,可以不支持原生库。
比如我使用默认的blank Activity创建了一个名为ExistingApplication的项目。
2 主要步骤
如果需要在现有项目中添加原生代码,需要参考下面步骤:(下列步骤推荐用于添加全新的库)
- 创建native源文件,并添加到工程中;
如果你仅仅是导入预构建的原生库或者源码可以跳过此步骤。 - 创建一个CMake构建脚本,用于构建你的源代码并创建库。如果是导入或者链接预构建的库也需要该脚本。
如果你的原生库已经使用CMakeLists.txt构建脚本,或者使用ndk-build构建并包含Android.mk构建脚本,可以跳过此步骤。 - 将Gradle与原生库链接起来,只提供下CMake或ndk-build脚本文件所在路径即可。
Gradle会自动使用构建脚本导入源代码,并将其以SO文件的形式打包到APK中。
通过上面三个步骤,你就可以在AS中使用JNI框架调试和验证原生代码了。
下面分开介绍下各个步骤。
步骤一:创建native源文件,并添加到工程中
在app模块中创建cpp目录用于保存新的原生代码,步骤如下:
- 切换到Project Files视图,并找到模块>src,在main目录上右键点击,选择新建>目录。
- 输入一个目录名字(比如输出cpp),并点击确定。
- 右键点击你刚创建的目录,并选择新建 > C/C++ 源文件。
- 输入要创建的源文件的名字,比如native_lib。
- 从下拉列表框中,选择源文件的扩展名,比如.cpp。
- 如果需要创建头文件,勾选对应的复选框。点击确定即可。
步骤二:创建CMake构建脚本
CMake构建脚本是一个名为CMakeLists.txt的纯文本文件。本部分仅包含在CMake构建本地库时常用的基础命令,更详细的介绍建议参考CMake官方文档。
请注意:如果使用ndk-build,无需提供CMake构建脚本。你可以通过提供你的Android.mk文件所在的目录将其与Gradle链接起来。
以下步骤是如何创建CMake构建脚本:
- 打开IDE左侧的工程面板,并选择Project视图。
- 右键点击模块的根目录,并选择新建 > 文件。
- 输入CMakeLists.txt作为文件名,并点击确定。
创建之后,你就可以在构建脚本中添加CMake命令了。为了使用CMake构建原生库,需要在构建脚本中添加cmake_minimum_required()和add_library()命令:(如下所示)
# 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( # 指定库的名称
native-lib
# 设置库类型为共享库
SHARED
# 提供源码所在的路径
src/main/cpp/native-lib.cpp )
在构建脚本中使用add_library()添加源文件或库时,AS会在Project视图中自动显示对应的头文件。然而,为了让CMake可以在编译时定位头文件的目录,需要在CMake构建脚本中添加include_directories()命令,并指定头文件所在目录。
add_library(...)
# Specifies a path to native header files.
include_directories(src/main/cpp/include/)
CMake用于命名库文件的格式如下:liblibrary-name.so
例如,如果你在构建脚本中指定共享库的名字为"native-lib",CMkae将创建一个名为libnative-lib.so
的文件。但在Java代码载入库时,需要使用你在CMake构建脚本中指定的名字:
static {
System.loadLibrary("native-lib");
}
添加NDK APIs
Android NDK中提供了一系列有用的原生API及库。你可以在CMakeLists.txt脚本文件中使用这些API,只需要包含NDK库即可。
预构建的NDK库已经在安卓平台上存在,你不需要将其构建或打包到你的APK中。由于NDK库是CMake搜索路径的一部分,你也无须指定你的NDK安装目录,你只需要提供在你的原生库中所依赖的ndk名称即可。
在你的CMake构建脚本中添加find_library()命令,用于定位NDK库所在位置,并将其存储在变量中。你可以构建脚本的其他部分使用该变量来引用NDK库。例如:下面示例代码中定位了安卓相关的log支持库,并将其路径保存在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 )
为了让你的原生库可以调用log库中的函数,你需要使用target_link_libraries()来链接该库。
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中包含一些以源码形式存在的库,如果需要的话,你需要将其编译为原生库,通过在CMake构建脚本中添加相应的add_library()
命令即可。其中你可以使用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 )
# You need to link static libraries against your shared native library.
target_link_libraries( native-lib app-glue ${log-lib} )
添加其他预构建的库
添加预构建库和使用CMake构建一个原生库类似。不过你需要使用IMPORTED标志来告知CMake:你仅需要将该库导入到你的工程中:
add_library( imported-lib
SHARED
IMPORTED )
你需要指定库所在的路径,使用set_target_properties()命令:
一些库针对不同的CPU架构或ABI(应用程序二进制接口)提供了不同的版本,并将其保存到单独目录下。这种方法可以帮助库仅加载需要版本的库。如果需要在CMake构建脚本中添加多ABI版本的库,不需要为每个版本的库编写对应的命令,只需要使用ANDROID_ABI环境变量即可。这个变量中包含了NDK支持的默认ABI列表,或者包含一个你在Gradle中手工配置的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_directories()命令实现,这样就可包含你自己的头文件了:
include_directories( imported-lib/include/ )
为了将预构建的库连接到你自己的原生库中,需要在CMake构建脚本中将其添加到target_link_libraries()命令中:
target_link_libraries( native-lib imported-lib app-glue ${log-lib} )
为了将预构建的库打包到你的APK中,你需要手工配置Gradle,在sourceSets块中包含你的.so文件所在路径。在构建APK之后,你可以使用APK Analyzer验证Gradle究竟打包了那个库。
包含其他CMake工程
如果你想构建多个CMake工程并将其输出导入到你的Android工程中,你可以将CMakeLists.txt作为顶级CMake构建脚本(同时在Gradle中指定该文件),然后将需添加的CMake工程作为该脚本的依赖项。下面的顶级CMake构建脚本使用add_subdirectory()命令来指定另一个CMakeLists.txt文件作为构建依赖项,然后将其输出链接到当前工程中,这样工作机制和预构建库类似。
# 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 )
步骤三:Gradle链接原生库
为了在Gradle中了链接你的原生库,你需要提供你的CMake或nkd-build脚本文件的路径。配置好之后,在实际构建APP时,Gradle会将CMake/ndk-build作为依赖项,并将共享库打包到APK中。Gradle也会参考该构建脚本,将源文件加载到Android Studio工程,以便你可以直接访问。如果你的native源代码没有对应的CMake构建脚本,你需要参考上一个步骤创建一个。
Android工程总的每个module只能链接一个CMake/ndk-build脚本文件。因此,假如你想同时构建多个CMake工程并打包其输出,你可用一个CMakeLists.txt作为顶层的CMake构建脚本(并在Gradle中指定该文件路径),然后将其他CMake工程作为该构建脚本的依赖项。同样的,如果你使用ndk-build,你可以在顶层的Android.mk脚本文件中包含其他Makefile。
当你在Gradle中配置好native项目之后,Android Studio将会更新Project面板来显示你的源代码文件和原生库(cpp),在External Build Files中显示外部构建脚本。
使用Android Studio UI配置
你可以使用Android Studio UI来实现将Gradle链接到CMake/ndk-build。
- 打开IDE左侧的Project面板,并选择Android视图。
- 右键点击需要链接原生库的module,比如app module,然后从菜单中选在Link C++ Project with Gradle。你将看到类似图1的对话框。
- 从下拉列表框中选择CMake或ndk-build。
- 如果使用CMake,在Project Path右侧可以选择你需要添加的CMakeLists.txt脚本文件。
- 如果使用ndk-build, 使用Project Patch右侧的选择对话框选择Android.mk脚本文件。如果Application.mk位于和Android.mk相同的目录,也会被自动加载。
- 点击确定即可。完成配置。
手动配置Gradle
手动配置Gradle,需要在你的module层的build.gradle文件中添加externalNativeBuild块,并将其使用cmake或ndkBuild块配置。
android {
...
defaultConfig {...}
buildTypes {...}
// Encapsulates your external native build configurations.
externalNativeBuild {
// Encapsulates your CMake build configurations.
cmake {
// Provides a relative path to your CMake build script.
path "CMakeLists.txt"
}
}
// If you want Gradle to package prebuilt native libraries
// with your APK, modify the default source set configuration
// to include the directory of your prebuilt .so files as follows.
sourceSets {
main {
jniLibs.srcDirs 'imported-lib/src/', 'more-imported-libs/src/'
}
}
}
指定可选配置项
你可以为CMake或ndk-build指定可选参数和标志,通过配置你的module层的build.gradle文件中的defaultConfig块下的externalNativeBuild子块。和defaultConfig块中其它属性类似,你可以在你的产品级构建配置中覆盖这些属性。
例如,如果你的CMake或ndk-build工程定义了多个原生库,你可以使用target属性来为特定产品构建和打包一个这些库的子集。下面代码展示了如何通过配置该属性实现上述需求:
android {
...
defaultConfig {
...
// This block is different from the one you use to link Gradle
// to your CMake or ndk-build script.
externalNativeBuild {
// For ndk-build, instead use the ndkBuild block.
cmake {
// Passes optional arguments to CMake.
arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
// Sets optional flags for the C compiler.
cFlags "-fexceptions", "-frtti"
// Sets a flag to enable format macro constants for the C++ compiler.
cppFlags "-D__STDC_FORMAT_MACROS"
}
}
}
buildTypes {...}
productFlavors {
...
demo {
...
externalNativeBuild {
cmake {
...
// Specifies which native libraries to build and package for this
// product flavor. If you don't configure this property, Gradle
// builds and packages all shared object libraries that you define
// in your CMake or ndk-build project.
targets "native-lib-demo"
}
}
}
paid {
...
externalNativeBuild {
cmake {
...
targets "native-lib-paid"
}
}
}
}
// Use this block to link Gradle to your CMake or ndk-build script.
externalNativeBuild {
cmake {...}
// or ndkBuild {...}
}
}
了解更多相关信息,请参考Configure Build Variants。对于CMake支持的参数属性,请参考CMake Variables。
指定ABIs
默认情况下,Gradle会按照NDK支持的ABI将你的原生库编译为独立的.so文件。并将它们全部打包到APK中。如果你希望Gradle仅构成和打包指定ABI版本的原生库,你就需要在module层的build.gradle文件中指定ndk.abiFilters标志,如下所示:
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {...}
// or ndkBuild {...}
}
// Similar to other properties in the defaultConfig block,
// you can configure the ndk block for each product flavor
// in your build configuration.
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your APK.
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
'arm64-v8a'
}
}
buildTypes {...}
externalNativeBuild {...}
}
如果你希望减小APK的大小,请参考基于ABI的多APK打包配置。
3 小结及后续
本文作为Android创建的第二篇,整体比较简单,内容主要是翻译部分,整理并介绍了源代码和gradle、CMake构建脚本。接下来我们将关注如何使用AS构建JAR包。
----------------------------------------------------------------------------------------------------------------------------
本文作者:Tocy e-mail: zyvj@qq.com
版权所有@2015-2020,请勿用于商业用途,转载请注明原文地址。本人保留所有权利。