打包方式:CMake
需要的插件:NDK,LLDB
首先下载好以上环境
如果上图列表中没有CMake,LLDB等选项,很可能的原因是Android SDK Tools有问题,需要重新下载Android SDK Tools。
需要打包的.so库关系比较复杂,出现了动态库依赖动态库的情况,我们就以下例为准:
本次,我需要打一个名称为impact-lib的.so。这个库要链接libshape.so, libtile.so,libmif.so, libcar.so。
这些库是由car,mif,shape,tile4个c++工程生成的。
car,mif,shape,tile是从第三方拿到的源码,是在linux平台下建立的标准c++工程,每个目录都是独立的工程,他们之间又有依赖关系。关系如下:
libshape.so不依赖任何其他库
libtile.so依赖libshape.so
libmif.so依赖libshape.so, libtile.so
libcar.so依赖libshape.so, libtile.so, libmif
为此,我建立了gen-libs模块专门用于提前生成上边4个库,模块的结构如上图,在src/main/下建立cpp目录,并把car,mif,shape,tile工程拷贝进去。
在cpp目录下手动建立CMakeLists.txt文件
gen-libs模块需要以下配置:
其中abiFilters可自行配置平台目标
cppFlags表示对c++11的支持
cmake.path配置成上一步建立好的CMakeLists.txt所在的相对路径,相对路径指的是相对于build.gradle。
确保以下路径配置是正确的:
添加的模块都在这里出现:
手动建立输出目录,以便把要依赖的.so打包到这里:
首先明确一下,libshape.so,libtile.so, libmif.so, libcar.so 4个库是可一次性生成的,只要安排好他们的生成顺序就行了,一个原则是,被依赖的库一定要先打。
在工程根目录下,新建目录distribution,用来作为.so的输出目录和保存.h文件的位置:
每个目录下的lib会生成对应平台的.so文件。
首先我们必须先打libshape.so,为此需要在shape目录下新建CMakeLists.txt:
内容如下:
固定的几个步骤:
CMAKE_MINIMUM_REQUIRED选定cmake版本为3.4.1
PROJECT为工程命名为shape
INCLUDE_DIRECTORIES 用来包含.h所在的目录,是相对于此CMakeLists文件的相对目录。
头两个SET定义了头文件和源文件的变量
ADD_LIBRARY SHARED 表示导出为动态链接库,名称为工程名,但实际输出系统会自动在前边加上lib,最终名为libshape.so
SET(distribution_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../distribution)定义名称为distribution_DIR的变量,表示当前CMakeLists文件相对的输出目录,也就是我们之前建立过的distribution
set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${distribution_DIR}/${PROJECT_NAME}/lib/${ANDROID_ABI}")
表示设定生成的输出目录为distribution_DIR,会在distribution下生成/lib/对应平台/libshape.so
回到cpp目录下的CMakeLists文件
添加内容如下:
set(lib_src_DIR ${CMAKE_CURRENT_SOURCE_DIR}) 设置当前源文件所在目录,其实就是相对于当前CMakeLists文件的目录,比如要编译shape工程时,就为/shape
set(lib_build_DIR $ENV{HOME}/tmp) 声明lib_build_DIR变量,指向环境变量HOME/tmp目录 file(MAKE_DIRECTORY ${lib_build_DIR})创建lib_build_DIR目录
我们可以理解为,打包过程中需要的临时目录。
add_subdirectory(${lib_src_DIR}/shape ${lib_build_DIR}/shape) 表示打包shape工程到临时目录,最终会输出到目标目录。
如果现在直接选择build
我们会在distribution目录下看到生成了一些文件如下:
一个简单的.so库就打出来了。如果我们想继续打其他工程,遵循以上步骤,但有个特殊地方需要注意,比如现在打libtile.so
按照上边的过程走一遍,然后编写对应的CMakeLists文件,内容如下:
我们发现规律都是一样的,只是增加了一些动态链接库引用语句
add_library(lib_shape SHARED IMPORTED) 表示引用动态链接库 set_target_properties(lib_shape PROPERTIES IMPORTED_LOCATION ${distribution_DIR}/shape/lib/${ANDROID_ABI}/libshape.so)
表示要引用的动态链接库的具体位置,也就是之前我们生成的目标位置
其中lib_shape是我们任意起的名字,表示引用动态链接库libshape.so
TARGET_LINK_LIBRARIES(${PROJECT_NAME} lib_shape )
表示打包时要链接的库,也就是上边定义的lib_shape
然后我们需要在cpp/CMakeLists文件中添加一句:
此时build一下,会发现distribution目录下生成了shape,tile对应的库,注意是一次性生成。
重复以上步骤,就可以生成所有的.so了
注意,需要链接多少个库,就需要添加多少条上边的过程,比如car工程的CMakeLists文件内容如下:
现在再次build,Distribution下已经生成了4种库。
所有被依赖的库已经准备好了,现在需要打libimpact-lib库,在此之前,分别拷贝car,mif,shape,tile工程下的.h,.inl文件到Distribution下对应目录/include下,此目的是为了编写c++文件时,能够include到指定的头文件。
App模块的结构如下:
首先新建一个ndk接口类Util,里边包含了一些本地方法:
配置app的build.gradle 如下:
jniLibs.srcDirs的意思是在生成apk的时候,把依赖的.so文件一并打进去。
Cmake path 配置成了上图cpp目录下的CMakeLists文件,此文件是由我们手动建立。
新增扩展工具,配置如下:
Parameters内容表示我要对app模块/src/main/java/下的类文件做操作,增加 encoding参数是为了生成文件时,格式正确,根据每个人机器的不同决定是否需要。
Working directory表示生成的.h文件目录为app模块/src/main/java/下。
在编写完Util类后,我们只需要在对应的类上右键,选择扩展工具:
此时会生成如下文件:
手动拷贝文件到cpp目录下
文件内容如下:
根据.h的内容,我们还需要新建对应的.cpp文件,Util.cpp,命名随自己定
在刚开始没有编写CMakeLists内容的时候,.cpp文件内如果include了要依赖的那些库的.h文件,可能会出现一堆红色错误提示,这是因为我们并未设定程序要包含的.h文件所在目录和库所在目录
为了包含正确,我们编写CMakeLists文件如下:
上边的语句相对大家已经不陌生了,之前已经都介绍过了,只是增加了对c++11的支持语句,还增加了对依赖库.h文件所在目录的包含。
target_include_directories包含.h或.inl文件所在目录,也就是一开始我们拷贝操作的那些目录
最后我们只要运行程序,系统会自动打包libimpact-lib.so到如下目录:
这是系统默认打包的目标位置,因为我们并没有在CMakeLists文件中指明打包目标。
至于ndk程序的调用,和jni语法的使用,此处略过。
参考资料:
https://cmake.org/documentation/
http://android-doc.com/
https://developer.android.com/ndk/index.html
http://www.open-open.com/doc/view/cf55b9aa480441e08e5ba89d9cc1f375
https://github.com/googlesamples/android-ndk/tree/master-cmake