前言:
本篇文章旨在简介 Android 中 NDK
是什么以及重点讲解最新 Android Studio 编译工具 CMake
的使用
在介绍 NDK
之前还是首推 Android 官方 NDK
文档。传送门
官方文档分别从以下几个方面介绍了 NDK
NDK
的基础概念NDK
项目ABI
是什么以及不同 CPU 指令集支持哪些 ABI
本节将会对文档进行总结和补充。所以建议先浏览一遍文档,或者看完本篇文章再回头看一遍文档。
JNI(Java Native Interface)Java本地接口,为了方便Java调用C、C++等本地代码所封装的一层接口(一个标准)
Java跨平台导致本地交互能力不强,一些和操作系统相关的特性Java无法完成,于是提供了jni用于和本地代码交互
上述部分文字摘自任玉刚的 Java JNI 介绍
NDK(Native Development Kit)原生开发工具包,帮助开发原生代码的工具,包括编译工具、一些公用库、开发IDE
NDK:提供了一套将c/c++编程成静态、动态库的工具,而Android.mk和application.mk(描述编译参数和一些配置文件)如指定使用c++11还是14编译,引用哪些共享库并描述关系,指定编译的abi,有了这些NDK中的编译工具才能准确的编译c/c++
ndk-build:Android NDK r4引入的shell脚本,调用正确的NDK构建脚本,最终还是会去调用NDK自己的编译工具
CMake:跨平台的编译工具,据自定义语音规则CMakeLists.txt生成对应makefile或project文件,调用底层编译;
c/c++编译文件再不同平台不一样:unix下使用makefile文件编译,windows使用project编译
Android studio2.2后工具增加CMake支持,在Android Studio2.2后有2种选择编译c/c++代码,一个是ndk-build+android.mk+application.mk组合,一个是cmake+cmakelists.txt组合,这两与android与c/c++代码无关,只是不同的构建脚本和命令
Application binary interface,不同的CPU与指令的组合都有定义的ABI
程序只有遵循了这个接口规范才能在该CPU上运行,为了兼容多个CPU,需为不同CPU构建不同的库文件,对CPU来说,不同的架构不一定不兼容:
规律:
具体的兼容问题可以参见这篇文章。Android SO文件的兼容和适配
介绍CMake
的规则和使用,以及如何使用 CMake
编译自己及其他预建的库。
首先创建一个新的包含原生代码的项目。在 New Project 时,勾选 Include C++ support:
项目创建好以后我们可以看到和普通Android项目有以下4个不同。
main
下面增加了 cpp
目录,即放置 c/c++ 代码的地方build.gradle
有修改CMakeLists.txt
文件.externalNativeBuild
目录
android {
...
defaultConfig {
...
externalNativeBuild {
//CMake 的命令集成在externalNativeBuild
cmake {
cppFlags "-frtti -fexceptions"
//CMake 的命令参数
arguments "-DANDROID_ARM_NEON=TRUE"
}
}
}
buildTypes {
...
}
externalNativeBuild {
cmake {
//指明了 CMakeList.txt 的路径
path "CMakeLists.txt"
}
}
}
...
更多的可以填写的命令参数和含义可以参见Android NDK-CMake文档
主要定义了哪些文件需要编译,以及和其他库的关系等
cmake_minimum_required(VERSION 3.4.1)
# 编译出一个动态库 native-lib,源文件只有 src/main/cpp/native-lib.cpp
add_library( # Sets 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 )
# 找到预编译库 log_lib 并link到我们的动态库 native-lib中
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
一个最基本的 CMakeLists.txt
,其实 CMakeLists.txt
里面可以非常强大,比如自定义命令、查找文件、头文件包含、设置变量等等。建议结合 CMake
的官方文档使用。同时在这推荐一个中文翻译的简易的CMake手册
当你需要引入已有的静态库/动态库(FFMpeg)或者自己编译核心部分并提供出去时就需要考虑如何在 CMake
中使用自己及其他预建的库
Android NDK 官网的使用现有库的文档中还是使用 ndk-build
+ Android.mk
+ Application.mk
组合的说明文档。(其实官方文档中大部分都是的,并没有使用 CMake
)
Github上的官方示例 里面有个项目 hello-libs 实现了如何创建出静态库/动态库,并引用它。
$project/distribution/
中使用一个静态库和一个动态库;$project/distribution/
目录,你不需要再编译这个库,二进制文件已经保存在了项目中。当然,如果有需要你也可以编译自己的源码,只需要去掉 setting.gradle
和 app/build.gradle
中的注释,然后执行一次,接着注释回去,防止在 build 的过程中不受影响。
我们采用自底向上的方式分析模块,先看下 gen-libs
模块。
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
//-DANDROID_PLATFORM 代表编译的 android 平台,直接设置 minSdkVersion 就ok,这个参数可忽略
arguments '-DANDROID_PLATFORM=android-9',
'-DANDROID_TOOLCHAIN=clang'
//2种编译工具链 - clang(默认) 和 gcc(废弃)
// explicitly build libs,编译哪些项目,默认都编译
targets 'gmath', 'gperf'
}
}
}
...
}
...
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
set(lib_src_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(lib_build_DIR $ENV{HOME}/tmp)
file(MAKE_DIRECTORY ${lib_build_DIR})
//为构建添加一个子路径,子路径中的 CMakeLists.txt 也会被执行
add_subdirectory(${lib_src_DIR}/gmath ${lib_build_DIR}/gmath)
add_subdirectory(${lib_src_DIR}/gperf ${lib_build_DIR}/gperf)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
//编译出一个静态库,源文件是 src/gmath.c
add_library(gmath STATIC src/gmath.c)
# copy out the lib binary... need to leave the static lib around to pass gradle check
set(distribution_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../distribution)
//是设置目标的一些属性来改变它们构建的方式
//gmath 的 ARCHIVE_OUTPUT_DIRECTORY 属性,改变了输出路径
set_target_properties(gmath
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY
"${distribution_DIR}/gmath/lib/${ANDROID_ABI}")
//自定义命令
# copy out lib header file...
add_custom_command(TARGET gmath POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E
copy "${CMAKE_CURRENT_SOURCE_DIR}/src/gmath.h"
"${distribution_DIR}/gmath/include/gmath.h"
# **** the following 2 lines are for potential future debug purpose ****
# COMMAND "${CMAKE_COMMAND}" -E
# remove_directory "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "Copying gmath to output directory")
以上就是一个静态库/动态库的编译过程。总结以下3点
app
模块是如何使用预建好的静态库/动态库的cmake_minimum_required(VERSION 3.4.1)
# configure import libs
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)
# 创建一个静态库 lib_gmath 直接引用libgmath.a
add_library(lib_gmath STATIC IMPORTED)
set_target_properties(lib_gmath PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/gmath/lib/${ANDROID_ABI}/libgmath.a)
# 创建一个动态库 lib_gperf 直接引用libgperf.so
add_library(lib_gperf SHARED IMPORTED)
set_target_properties(lib_gperf PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/gperf/lib/${ANDROID_ABI}/libgperf.so)
# build application's shared lib
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
# 创建库 hello-libs
add_library(hello-libs SHARED
hello-libs.cpp)
# 加入头文件
target_include_directories(hello-libs PRIVATE
${distribution_DIR}/gmath/include
${distribution_DIR}/gperf/include)
# hello-libs库链接上 lib_gmath 和 lib_gperf
target_link_libraries(hello-libs
android
lib_gmath
lib_gperf
log)
可以看下基本上分成了4个步骤引入:
hello-libs
编辑好并 Sync
后,你就可以发现 hello-libs
中的c/c++代码可以引用暴露的头文件调用内部方法了
首推 Android NDK 官方文档,虽然很多都不完整,但是绝对是必须看一遍的东西。
当初次接触 NDK
开发又觉得新建的 Hello World 项目过于简单时。建议把 googlesamples - android-ndk 项目拉下来。里面有多个实例参考,比官方文档完整很多。
当你发现示例里的一些NDK配置满足不了你的需求后,你就需要到 CMake 官方文档 去查询完整的支持的函数,同时这里也提供一个中文翻译的简易的CMake手册。
以上文档资料仅为了解决 NDK 开发过程中编译配置问题,具体 c/c++ 的逻辑编写、jni等不在此范畴。
文末献上一组彩蛋,将 CMake
或者 NDK
开发过程中遇到的坑和小技巧以 Q&A 的方式列出。持续更新https://www.jianshu.com/p/6332418b12b1
Q1:怎么指定 C++标准?
A:在 build_gradle
中,配置 cppFlags -std
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions -std=c++14"
arguments '-DANDROID_STL=c++_shared'
}
}
Q2:add_library 如何编译一个目录中所有源文件
A: 使用 aux_source_directory
方法将路径列表全部放到一个变量中。
# 查找所有源码 并拼接到路径列表
aux_source_directory(${CMAKE_HOME_DIRECTORY}/src/api SRC_LIST)
aux_source_directory(${CMAKE_HOME_DIRECTORY}/src/core CORE_SRC_LIST)
list(APPEND SRC_LIST ${CORE_SRC_LIST})
add_library(native-lib SHARED ${SRC_LIST})
Q3:怎么调试 CMakeLists.txt 中的代码?
A:使用 message
方法
cmake_minimum_required(VERSION 3.4.1)
message(STATUS "execute CMakeLists")
...
运行后在
.externalNativeBuild/cmake/debug/{abi}/cmake_build_output.txt
中查看 log
Q4:什么时候 CMakeLists.txt 里面会执行?
A:测试了下,好像在 sync 的时候会执行。执行一次后会生成 makefile
的文件缓存之类的东西放在 externalNativeBuild
中。所以如果 CMakeLists.txt
中没有修改的话再次同步好像是不会重新执行的。(或者删除 .externalNativeBuild
目录)
真正编译的时候好像只是读取.externalNativeBuild
目录中已经解析好的 makefile
去编译。不会再去执行 CMakeLists.txt
作者:Tsy远
链接:https://www.jianshu.com/p/6332418b12b1
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
https://www.jianshu.com/p/6332418b12b1
在命令行下用cmake交叉编译可在android中运行的so包https://blog.csdn.net/minghuang2017/article/details/78938852
so(shared object,共享库)是机器可以直接运行的二进制代码,是Android上的动态链接库,类似于Windows上的dll。每一个Android应用所支持的ABI是由其APK提供的.so文件决定的,这些so文件被打包在apk文件的lib/目录下,其中abi可以是上面表格中的一个或者多个。