Android:JNI与NDK(三)NDK构建的脚本文件配置
友情提示:欢迎关注本人公众号,那里有更好的阅读体验以及第一时间获取最新文章
本文目录
一、前言
本篇我们介绍Android.mk与CMakeLists.txt构建NDK的配置文件,我们知道目前NDK的开发已经基本废弃Android.mk的使用了,AS创建NDK工程默认已经使用CMakeLists.txt构建文件,那我们为什么还要介绍Android.mk呢?
因为在平时开发中我们依然有可能接触到Android.mk文件,并且很多老的开源库依然使用的是Android.mk配置方式来构建的,这就要求我们能够读懂,在我们自己开发NDK的时候使用CMakeLists.txt来构建就可以了,Android.mk我们只需要读懂核心配置就可以了,遇到的时候能够读懂大部分意思,而CMakeLists.txt构建方式我们需要能够熟练使用。
上一篇中,我们在PC上编译动态库或者静态库的时候需要给编译器传递一些参数,比如头文件,库文件的查找路径,那种编译方式需要我们手动通过命令行的方式来编译,通常编译ffmpeg等提供源码的三方库为安卓平台可用的动态库或者静态库的时候会用这种方式,工作中我们也会自己在安卓项目中编写C/C++源文件,这些源文件怎么编译为动态库或者静态库呢?编译的时候我们用到三方的动态库或者静态库怎么设置依赖呢?怎么设置头文件或者库文件的查找路径呢?以及怎么配置编译文件之间的依赖呢?这些都可以在Android.mk或者CMakeLists.txt文件中配置,
本篇我们主要详细了解一下这些配置文件怎么配置,工作中出问题我们也好自己来查找解决。
好了,进入本文的学习。
二、Android.mk与Application.mk配置的了解
Android.mk的语法支持将源文件分组为模块。 模块是静态库、共享库或独立的可执行文件。 您可在每个Android.mk文件中定义一个或多个模块,也可在多个模块中使用同一个源文件。Android提供了一系列的内置变量来提供更加方便的构建语法规则。
Application.mk文件实际上是对应用程序本身进行描述的文件,它描述了应用程序要针对哪些CPU架构打包动态so包、要构建的是release包还是debug包以及一些编译和链接参数等。
Android.mk配置文件
我们先来看几个具体的Android.mk配置文件:
1 LOCAL_PATH := $(call my-dir) 2 3 include $(CLEAR_VARS) 4 5 LOCAL_MODULE := MyGame_shared 6 7 LOCAL_MODULE_FILENAME := libMyGame 8 9 #源文件 10 LOCAL_SRC_FILES := $(LOCAL_PATH)/A.cpp \ 11 $(LOCAL_PATH)/../../../Classes/B.cpp \ 12 $(LOCAL_PATH)/../../../Classes/C.cpp 13 14 # 编译时查找头文件的路径 相当于:-I 15 LOCAL_C_INCLUDES := $(LOCAL_PATH) \ 16 $(LOCAL_PATH)/include 17 18 # 需要链接的系统默认库 19 LOCAL_LDLIBS := -llog \ 20 -lz 21 22 include $(BUILD_SHARED_LIBRARY) 23 24 $(call import-add-path,$(LOCAL_PATH)/../../../aaa) 25 $(call import-add-path,$(LOCAL_PATH)/../../../aaa/bbb) 26 #引入其他路径下的Android.mk文件 27 # 相当于 #include 28 $(call import-module, ddd)
1 LOCAL_PATH := $(call my-dir) 2 3 include $(CLEAR_VARS) 4 LOCAL_MODULE := Test 5 LOCAL_SRC_FILES := libTest.a 6 include $(PREBUILT_STATIC_LIBRARY) 7 8 include $(CLEAR_VARS) 9 LOCAL_MODULE := hello-jni 10 LOCAL_SRC_FILES := hello-jni.c 11 # 编译hello-jni模块 需要链接 Test 模块 12 # Test模块是一个预编译库模块 13 LOCAL_STATIC_LIBRARIES := Test 14 include $(BUILD_SHARED_LIBRARY)
没接触或者看不懂没关系,接下来会解释的。
Android提供了一系列的内置变量或者函数来供我们更加方便的构建编译脚本,接下来我们来具体了解一下怎么使用或者配置:
1 LOCAL_PATH := $(call my-dir)
构建系统提供的宏函数 my-dir返回Android.mk 文件所在的目录,这里的意思就是LOCAL_PATH 指向Android.mk 文件所在的目录。
1 include $(CLEAR_VARS)
CLEAR_VARS 变量指向一个特殊的 GNU Makefile,后者会清除许多 LOCAL_XXX 变量,例如 LOCAL_MODULE、LOCAL_SRC_FILES 和 LOCAL_STATIC_LIBRARIES。每个模块编译描述之前都会调用一下include $(CLEAR_VARS),意思就是本模块不用你们之前模块定义的那些信息,我要自己定义,也就是将之前定义的变量值全部清空自己重新定义一下。
1 LOCAL_MODULE := hello-jni
LOCAL_MODULE 变量存储您要构建的模块的名称,且必须唯一不含空格,会对分配给 LOCAL_MODULE 的名称自动添加正确的前缀和后缀。 例如,上述示例会生成名为libhello-jni.so的库。如果模块名称的开头已经是 lib,则构建系统不会附加额外的 lib前缀;而是按原样采用模块名称,并添加 .so 扩展名。
1 LOCAL_MODULE_FILENAME := libnewfoo
LOCAL_MODULE_FILENAME 是一个可选变量,我们可以不配置,如果我们给这个变量配置了名称,则会强制系统将其生成的文件命名为LOCAL_MODULE_FILENAME 所配置的名称,也就是LOCAL_MODULE_FILENAME 的优先级高于LOCAL_MODULE ,比如,我们配置如下:
1 LOCAL_MODULE := foo 2 LOCAL_MODULE_FILENAME := libnewfoo
对于生成动态库,生成的名称是libnewfoo.so而不是libfoo.so。
1 LOCAL_SRC_FILES := hello-jni.c
这里配置源文件的列表,多个文件以空格隔开,也可以 \ 来换行配置
1 include $(BUILD_SHARED_LIBRARY)
指导生成动态库还是静态库,或者是预编译库,预编译库就是已经生成的动态或者静态库,可选配置如下:
名称 | 说明 |
---|---|
BUILD_SHARED_LIBRARY | 生成动态库 |
BUILD_STATIC_LIBRARY | 生成静态库 |
PREBUILT_SHARED_LIBRARY | 预编译的库是动态库 |
PREBUILT_STATIC_LIBRARY | 预编译的库是静态库 |
1 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
指定C/C++编译额外头文件的目录
1 LOCAL_CFLAGS += -I<path>
此可选变量用于设置在构建 C 和 C++ 源文件时构建系统要传递的编译器标记,可使用此功能指定额外的宏定义或编译选项。
1 LOCAL_CPPFLAGS += -I<path>
只构建 C++ 源文件时将传递的一组可选编译器标记, 这些标记会出现在编译器的命令行中,跟在 LOCAL_CFLAGS 之后。
LOCAL_CFLAGS 与LOCAL_CPPFLAGS 需要额外说明一下:这里传递的参数会直接传给编译器的命令行,上一篇我们讲解了给编译传递头文件,库文件查找路径的方式这里都可以配置。
1 LOCAL_STATIC_LIBRARIES := Test
此变量用于存储当前模块所依赖的静态库模块列表。比如编译此模块需要依赖libTest.a静态库,则上面配置即可。
1 LOCAL_SHARED_LIBRARIES
此变量用于存储当前模块所依赖的动态库模块列表。
1 LOCAL_LDLIBS := -llog \ 2 -lz
此变量包含额外的链接程序标记列表,供构建共享库或可执行文件时使用。 利用此变量,您可使用 -l 前缀传递特定系统库的名称,如果为静态库定义此变量,构建系统会将其忽略。 上面的指定表示编译此模块需要依赖系统的liblog.so与libz.so动态库。
1 $(call import-add-path,$(LOCAL_PATH)/../../../aaa) 2 $(call import-add-path,$(LOCAL_PATH)/../../../aaa/bbb) 3 $(call import-module, ddd)
import-module函数用于按模块名称来查找和包含模块的Android.mk文件, import-add-path用来添加查找的路径,二者常常配合使用,上面的意思就是查找ddd模块,并将此模块下的Android.mk文件配置引入引入进来,查找值前调用import-add-path方法来添加查找的路径。
好了以上就是Android.mk文件的一些核心配置,我们回过头来在看下上面提到的具体配置:
1 LOCAL_PATH := $(call my-dir) 2 3 #预编译库的引入 4 include $(CLEAR_VARS) 5 LOCAL_MODULE := Test 6 LOCAL_SRC_FILES := libTest.a 7 include $(PREBUILT_STATIC_LIBRARY) 8 9 include $(CLEAR_VARS) 10 LOCAL_MODULE := hello-jni 11 LOCAL_SRC_FILES := hello-jni.c 12 # 编译hello-jni模块 需要链接 Test 模块 13 # Test模块是一个预编译库模块 14 LOCAL_STATIC_LIBRARIES := Test 15 include $(BUILD_SHARED_LIBRARY)
在工程中hello-jni.c源文件中使用到了libTest.a静态库中方法,所以在编译hello-jni.c源文件为动态库的时候需要先预编译libTest.a静态库,并通过LOCAL_STATIC_LIBRARIES 指定引用的静态库。
好了Android.mk中的一些核心配置介绍到此,对于Android.mk文件工作中遇到能大体读明白就可以了。
Application.mk配置文件
接下来我们看下Application.mk配置文件有哪些核心配置,同样先来看一下实际例子:
1 APP_CPPFLAGS := -frtti 2 APP_LDFLAGS := -latomic 3 APP_ABI := armeabi-v7a 4 APP_PLATFORM = android-21 5 APP_OPTIM := debug
Application.mk中核心配置就少多了,主要是一些全局的配置,可作用于任何Android.mk中的编译单元。
我们看一下核心的配置表示的意义:
1 APP_CFLAGS += -I<PATH>
APP_CFLAGS 变量用于存储一组 C 编译器标记,供构建系统在为任何模块编译任何 C 或 C++ 源代码时传递到编译器。
1 APP_CPPFLAGS
此变量包含一组 C++ 编译器标记,仅供构建系统构建 C++ 源文件时传递到编译器。 使用 APP_CFLAGS 可以为 C 和 C++ 指定标记。
1 APP_OPTIM := debug
这个可选变量定义为 release 或 debug。 使用此变量可以在构建应用的模块时变更优化级别。
发布模式为默认模式,可以生成高度优化的二进制文件。 调试模式会生成未经优化的二进制文件,如此更容易调试。
1 APP_ABI := armeabi-v7a arm64-v8a
使用 APP_ABI 设置为特定的平台生成机器代码,多个值以空格隔开,如上面设置生成armeabi-v7a与 arm64-v8a平台架构下的动态或者静态库。
1 APP_PLATFORM = android-21
指定支持的最低版本的 Android 平台
好了,以上就是Application.mk中一些核心的配置,对于mk配置文件我们大概能读懂就可以了,很多我也没介绍,现在基本没人用了,相当于上古时候的工具,现在开发NDK基本全用的CMakeLists.txt方式了,接下来我们来看看CMakelists.txt的配置方式。
三、掌握CMakeLists.txt的配置
在android studio 2.2及以上,构建原生库的默认工具是 CMake。
CMake是一个跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。Cmake 并不直接建构出最终的软件,而是产生其他工具的脚本(如Makefile ),然后再依这个工具的构建方式使用。
CMake是一个比make更高级的编译配置工具,它可以根据不同平台、不同的编译器,生成相应的Makefile或者vcproj项目。从而达到跨平台的目的。
Android Studio利用CMake生成的是ninja,ninja是一个小型的关注速度的构建系统。我们不需要关心ninja是什么鬼,知道怎么配置cmake就可以了。
以上吧啦吧啦一堆就是说CMake怎么怎么的好,CMake 的配置文件是CMakeLists.txt,我们只需要关心怎么配置就好了,接下来我们看下具体的配置以及其含义。
单文件
如果我们工程只有一个C/C++文件,我们配置设置生成动态或者静态库,如下配置即可:
1 # 指定运行此配置文件所需的 CMake 的最低版本 2 cmake_minimum_required(VERSION 3.4.1) 3 4 # 该命令表示项目的名称是NDK 5 project(NDK) 6 7 #src/main/cpp/native-lib.cpp 会编译为libnative-lib.so动态库 8 #SHARED编译动态库 STATIC:静态库 9 add_library( native-lib 10 SHARED 11 src/main/cpp/native-lib.cpp)
上面已经给出详细解释,不再细说。
多文件
如果我们有多个C/C++源文件想生成动态或者静态库加入如下配置即可:
1 # 指定运行此配置文件所需的 CMake 的最低版本 2 cmake_minimum_required(VERSION 3.4.1) 3 # 该命令表示项目的名称是NDK 4 project(NDK) 5 6 # 多文件 7 file(GLOB DIR_SRCS src/main/cpp/*.c src/main/cpp/*.cpp) 8 # SHARED: 动态库 STATIC:静态库、 9 add_library(hello-jni SHARED ${DIR_SRCS})
经过上面配置就可以将src/main/cpp目录下的所有C/C++文件编译为一个so动态库。
预编译静态库的引入
如果我们编译自己源文件的时候用到了已经编译好的静态库中的方法等,就需要进行额外配置让编译器编译的时候能链接到对应静态库,比如我们将静态库放入如下目录:
配置如下:
1 # 指定运行此配置文件所需的 CMake 的最低版本 2 cmake_minimum_required(VERSION 3.4.1) 3 # 该命令表示项目的名称是NDK 4 project(NDK) 5 #src/main/cpp/native-lib.cpp 会编译为libnative-lib.so动态库 6 #SHARED编译动态库 STATIC:静态库 7 add_library( native-lib 8 SHARED 9 src/main/cpp/native-lib.cpp) 10 11 #引入预编译好的静态库 12 # IMPORTED: 表示静态库是以导入的形式添加进来(预编译静态库) 13 # StaticTest 可以随便起名字 14 add_library(StaticTest STATIC IMPORTED) 15 16 #设置静态库的导入路径 17 set_target_properties(StaticTest PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/static/armeabi-v7a/libStaticTest.a) 18 19 #生成native-lib动态库需要用到StaticTest log动态或者静态库 20 #链接的配置 21 target_link_libraries( # Specifies the target library. 22 native-lib 23 # libTest.so 可以去掉lib与.so 24 Test 25 log )
CMAKE_SOURCE_DIR 是系统预定义好的变量,代表当前CMakeLists.txt所在的目录。
上面我们进行编译的时候除了设置预编译的libStaticTest.a静态库,还设置了log库用于打印一些信息,但是log库我们并没有配置其路径,这是因为构建系统为我们已经编译好一些常用的库供我们使用,其查找路径与头文件路径已经预定义好了,编译器会自己去查找的,不用我们额外配置查找路径,但是我们使用了额外的预编译库,那就需要进行额外配置了。
动态库的引入
上面的方式其实同样可以引入动态库,如:
1 add_library(Test SHARED IMPORTED) 2 set_target_properties(Test PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libTest.so)
只需将STATIC改为SHARED即可,但是这样的配置方式在6.0及以上系统会报找不到库路径的错误,CMake提供了另一种配置方式:
1 #动态库这样引入没有版本差异,如果像上面那样引入会有版本问题 2 # CMAKE_CXX_FLAGS 会传给c++编译器 3 # CMAKE_C_FLAGS 会传给c编译器 4 # CMAKE_SOURCE_DIR 的值是当前CMakelist.txt所在目录 5 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a")
还记得-L参数的意义吗?上一篇讲过就是给编译器传递库文件查找路径的,别忘了链接的时候添加动态库名字:
1 target_link_libraries( # Specifies the target library. 2 native-lib 3 Test 4 StaticTest 5 log )
额外头文件路径查找配置以及其余CMakeLists.txt配置文件的引入
1 #额外头文件查找路径设置 相当于-I 2 include_directories(src/main/include) 3 4 #引入src/main/subcmakelist目录的cmakelist 5 add_subdirectory(src/main/subcmakelist)
build.gradle中有关NDK编译的配置
主要如下:
1 android { 2 compileSdkVersion 26 3 defaultConfig { 4 applicationId "com.wanglei55.ndk" 5 minSdkVersion 18 6 targetSdkVersion 26 7 versionCode 1 8 versionName "1.0" 9 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 10 externalNativeBuild { 11 cmake { 12 //指导我们自己编写的c/c++生成动态或者静态库时生成几种架构的库 13 abiFilters "armeabi-v7a" 14 } 15 } 16 //应该打包几种cpu 17 //比如:三方库中提供了arm的提供了x86的,可以在此处指导只打包arm,生成出来的apk就只会包含 arm的 18 ndk{ 19 abiFilters "armeabi-v7a" 20 } 21 } 22 buildTypes { 23 release { 24 minifyEnabled false 25 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 26 } 27 } 28 externalNativeBuild { 29 cmake {//设置CMakeLists.txt所在目录,从build.gradle所在目录开始查找 30 path "CMakeLists.txt" 31 } 32 } 33 }
好了,以上就是关于CMakeLists.txt核心配置的一些讲解,此部分都是NDK开发中的一些基础知识,学起来可能比较枯燥,但是却是很重要的,否则后面的三方库的编译配置就直接无从下手,磨刀不误砍柴工,先把基础的学好吧。
CMake还有一些其余配置,可参考官方说明:官方说明
四、总结
本篇,我们讲解了一下mk与CMakeLists.txt的一些核心配置,主要为了以后我们看别人写的或者自己需要配置的时候能知道怎么下手,可能比较枯燥,但是这是NDK部分必须要搞懂的。
本篇到此为止。