使用CMake进行NDK开发简记。
前言
最近在开发项目的时候,部分网络请求的加密规则由于一直写在Java
代码中,很容易被反编译找到关键加密规则导致被爬取数据,所以在评估了加密方案之后决定把加密规则由C++
实现,通过NDK
编译之后提供给Java
层使用。
NDK
编译的方式有:
本文记录CMake
编译。
CMake
开始使用CMake
安装CMake
在开始项目前,确保已安装如下程序:
其中LLDB可以用于调试C++
代码。
指定NDK目录。
编写C++文件
借鉴官方例子。
CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#设置编译 native library 需要最小的 cmake 版本
cmake_minimum_required(VERSION 3.4.1)
#将指定的源文件编译为名为 libnative-lib.so 的动态库
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).
native-lib.cpp )
#查找本地 log 库
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} )
native-lib.cpp
1
2
3
4
5
6
7
8
9
10
11
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_cmake_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
Gradle配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.cmake"
minSdkVersion 17
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
}
ndk {
//指定ABI为armeabi-v7a
abiFilters "armeabi-v7a"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
}
Activity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sample_text.text = stringFromJNI()
}
external fun stringFromJNI(): String
companion object {
init {
System.loadLibrary("native-lib")
}
}
}
至此,C++代码已经正常执行。
CMake编译so
这个时候直接make project可以在目录工作目录\build\intermediates\cmake\debug
下看到各ABI对应的so文件。
CMake指令
注:指令与大小写无关,官方建议使用大写,不过 Android 的 CMake 指令是小写的,下面为了便于阅读,采取小写的方式。
project
语法:project( [CXX] [C] [Java])
这个指令是定义工程名称的,并且可以指定工程支持的语言(当然也可以忽略,默认情况表示支持所有语言),不是强制定义的。例如:project(HELLO)
定义完这个指令会隐式定义了两个变量:
<projectname>_BINARY_DIR
和<projectname>_SOURCE_DIR
,
MESSAGE
指令有用到这两个变量;
另外 CMake 系统还会预定义了 PROJECT_BINARY_DIR
和 PROJECT_SOURCE_DIR
变量,它们的值和上面两个的变量对应的值是一致的。不过为了统一起见,建议直接使用PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
,即使以后修改了工程名字,也不会影响两个变量的使用。
set
语法:set(VAR [VALUE])
这个指令是用来显式地定义变量,多个变量用空格或分号隔开
例如:
set(SRC_LIST main.c test.c)
注意,当需要用到定义的 SRC_LIST 变量时,需要用${var}的形式来引用,如:${SRC_LIST} 不过,在 IF 控制语句中可以直接使用变量名。
message
语法:message([SEND_ERROR | STATUS | FATAL_ERROR] “message to display” … )
这个指令用于向终端输出用户定义的信息,包含了三种类型:
SEND_ERROR
,产生错误,生成过程被跳过;
STATUS
,输出前缀为—-的信息;
FATAL_ERROR
,立即终止所有 CMake 过程;
add_executable
语法:add_executable(executable_file_name [source])
将一组源文件 source 生成一个可执行文件。 source 可以是多个源文件,也可以是对应定义的变量
如:
add_executable(hello main.c)
cmake_minimun_required(VERSION x.x.x)
用来指定 CMake
最低版本为x.x.x,如果没指定,执行 cmake
命令时可能会出错。
add_subdirectory
语法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL
参数含义是将这个目录从编译过程中排除。
另外,也可以通过 SET 指令重新定义 EXECUTABLE_OUTPUT_PATH
和 LIBRARY_OUTPUT_PATH
变量来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成的中间文件)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
add_library
语法:add_library(libname [SHARED | STATIC | MODULE] [EXCLUDE_FROM_ALL] [source])
将一组源文件 source
编译出一个库文件,并保存为 libname.so
(lib 前缀是生成文件时 CMake自动添加上去的)。其中有三种库文件类型,不写的话,默认为 STATIC:
- SHARED: 表示动态库,可以在(Java)代码中使用
System.loadLibrary(name)
动态调用; - STATIC: 表示静态库,集成到代码中会在编译时调用;
- MODULE: 只有在使用 dyId 的系统有效,如果不支持 dyId,则被当作 SHARED 对待;
- EXCLUDE_FROM_ALL: 表示这个库不被默认构建,除非其他组件依赖或手工构建
将compress.c 编译成 libcompress.so 的共享库 add_library(compress SHARED compress.c)
add_library
命令也可以用来导入第三方的库:
add_library(libname [SHARED | STATIC | MODULE | UNKNOWN] IMPORTED)
如,导入 libjpeg.so
add_library(libjpeg SHARED IMPORTED)
导入库后,当需要使用 target_link_libraries
链接库时,可以直接使用该库。
find_library
语法:find_library( name1 path1 path2 …)
VAR
变量表示找到的库全路径,包含库文件名 。例如:
find_library(libX X11 /usr/lib) find_library(log-lib log) #路径为空,应该是查找系统环境变量路径
set_target_properties
语法: set_target_properties(target1 target2 … PROPERTIES prop1 value1 prop2 value2 …)
这条指令可以用来设置输出的名称(设置构建同名的动态库和静态库,或者指定要导入的库文件的路径),对于动态库,还可以用来指定动态库版本和 API 版本。
如,
set_target_properties(hello_static PROPERTIES OUTPUT_NAME “hello”)
include_directories
语法:include_directories([AFTER | BEFORE] [SYSTEM] dir1 dir2…)
这个指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的
后面。
target_link_libraries
语法:target_link_libraries(target library library2…)
这个指令可以用来为 target 添加需要的链接的共享库,同样也可以用于为自己编写的共享库添加共享库链接。
如:
#指定 compress 工程需要用到 libjpeg 库和 log 库 target_link_libraries(compress libjpeg ${log-lib})