Android NDK 知识大全

引用

基本概念

NDK安装配置

资料整理

无论是用Android Studio集成NDK,还是手动使用NDK命令行,有两个文件是必须的

  • Android.mk: 在jni文件夹内必须创建这个文件,ndk-build命令也会首先查找此文件才能执行,文件里定义ndk编译的模块的名称、c/c++源代码,需要链接的库及构建选项(传递给编译工具gcc|clang等的参数)。
  • Application.mk: 这个文件定义需要构建的模块和构建环境及工具(如果是集成在 android studio里,部分选项会被 gradle覆盖),包括以下:
    • 需要编译的目标平台ABI
    • 工具链接
    • 需要包含的标准库(static and dynamic STLport or default system).
    • 全局的编译选项

Android.mk

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LOCAL_PATH := $(call my-dir) # 必须要使用这个开头
include $(CLEAR_VARS) # 必须,清除编译环境中所有的LOCAL变量,否则同时编译多个Module时不同的Android.mk里的LOCAL变量会相互影响

LOCAL_MODULE := mcpelauncher # 必须,定义模块名称
# 必须,源文件文件列表
LOCAL_SRC_FILES := nativepatch.c modscript.c modscript_nextgen.cpp modscript_ScriptLevelListener.cpp utf8proc_slim.c dobby.cpp

LOCAL_C_INCLUDES += E:\C_Shared\sdk\ndk-bundle\platforms\android-14\arch-arm\usr\include\

LOCAL_LDLIBS := -llog # 需要链接的NDK库
LOCAL_SHARED_LIBRARIES := tinysubstrate-bin

# 必须,包含定义好的一个文件,获取抽有LOCAL变量并决定构建信息
include $(BUILD_SHARED_LIBRARY)

$(call import-add-path, prebuilts) # 其它导入模块
$(call import-module, tinysubstrate-bin)

变量和宏

命名规则:

  • LOCAL_: NDK编译系统使用的,提供给用户设置值
  • PRIVATE、NDK、APP:NDK编译系统内部使用,用户不要定义
  • my-dir:一些小写名称,编译系统内部使用的
  • MY_: 其它大写名称,是用户自定义(仅仅是推荐,用户可以使用其它前缀或不使用前缀)

NDK预定义变量

  • CLEAR_VARS include $(CLEAR_VARS)
  • BUILD_SHARED_LIBRARY | BUILD_STATIC_LIBRARY include $(BUILD_SHARED_LIBRARY)
  • PREBUILT_SHARED_LIBRARY | PREBUILT_STATIC_LIBRARY
  • TARGET_ARCH 目标cpu架构 取值arm、x86等
  • TARGET_PLATFORM 目标android版本 TARGET_PLATFORM := android-22
  • TARGET_ARCH_ABI 目标cpu及架构的全名 如:TARGET_ARCH_ABI := arm64-v8a x86,详表见官方文档中TARGET_ARCH_ABI一节。
  • TARGET_ABI Android Api级别和ABI的组合

模块定义变量

这些变量是每个模块单独设置的,每个模块都需要做以下事情:

  1. 初使化模块变量或者用CLEAR_VARS清除
  2. 给一些变量赋值来描述模块的功能
  3. 使用NDK预定义变量中的BUILD_XXX来定义模块的格式(动态库、静态库等)

变量列表(粗体为必须):

  • LOCAL_PATH 设置当前文件的路径,必须在Androd.mk的开头使用LOCAL_PATH := $(call my-dir)
  • LOCAL_MODULE 当前模块名称,不能用空格。除了CLEAR_VARS之外他要放在前面,不需要添加lib前缀和.so.a后缀。
  • LOCAL_MODULE_FILENAME 模块文件名,可以指定一个新的模块文件名
  • LOCAL_SRC_FILES 源文件文件列表
  • LOCAL_CPP_EXTENSION 定义支持的c++源文件的后缀,一般来说可以用LOCAL_CPP_EXTENSION := .cxx .cpp .cc
  • LOCAL_CPP_FEATURES c++语言特性,如rtti、exceptions等
  • LOCAL_C_INCLUDES 头文件默认是相对于NDK的根目录,如果相对于当前目录要用LOCAL_C_INCLUDES := $(LOCAL_PATH)//foo,需要设置在LOCAL_CFLAGS和LOCAL_CPPFLAGS 之前。ps:在windows上我想直接引用ndk目录,但使用了LOCAL_PATH后似乎就默认是当前目录了, 所以我最后使用了环境变量中的ndk目录。例:LOCAL_C_INCLUDES += $(ANDROID_NDK_HOME)\platforms\$(TARGET_PLATFORM)\arch-$(TARGET_ARCH)\usr\include
  • LOCAL_CFLAGS 给c和c++的编译选项,从 android-ndk-1.5_r1开始,此选项仅适用于c语言
  • LOCAL_CPPFLAGS 给c++的编译选项
  • LOCAL_STATIC_LIBRARIES 当前模块依赖的静态库,如果当前模块也是静态库,这个表明引用当前模块的也自动依赖这些静态库。如果当前模块是可执行程序或动态库,则链接这些静态库。
  • LOCAL_SHARED_LIBRARIES 运行时依赖的动态库,仅用在链接时
  • LOCAL_LDLIBS 声明链接时引用的动态库,-l作为前缀,例LOCAL_LDLIBS := -lz声明需要链接/system/lib/libz.so
  • LOCAL_LDFLAGS 链接参数,如果当前模块是是静态库,此参数无效
  • LOCAL_ALLOW_UNDEFINED_SYMBOLS 当设置成true时,链接时不检查未定义的引用。如果当前模块是是静态库,此参数无效
  • LOCAL_WHOLE_STATIC_LIBRARIES、LOCAL_ARM_MODE、LOCAL_ARM_NEON、LOCAL_DISABLE_NO_EXECUTE、LOCAL_DISABLE_RELRO、LOCAL_DISABLE_FORMAT_STRING_CHECKS、LOCAL_SHORT_COMMANDS、LOCAL_THIN_ARCHIVE、LOCAL_FILTER_ASM等 略,详见文档
  • LOCAL_EXPORT_XXX系列指令

预定义函数

  • my-dir 获取最后一个makefile的目录
  • all-subdir-makefiles 返回所有子目录中的Android.mk
  • this-makefile 当前makefile的路径
  • parent-makefile 上级makefile的路径
  • grand-parent-makefile 上上级makefile的路径
  • import-module 使用方式$(call import-module,<name>),在NDK_MODULE_PATH环境变量中查找指定名称的模块,然后inlucde它的Android.mk文件

Application.mk

示例:

1
2
3
4
5
6
7
APP_ABI := x86 #只生成x86架构的CPU用的lib,要生成所有平台的可以改为all    
#APP_STL := stlport_static
NDK_TOOLCHAIN_VERSION=4.7 #使用GCC4.7
APP_STL := gnustl_static #GNU STL
APP_CPPFLAGS := -fexceptions -frtti #允许异常功能,及运行时类型识别
APP_CPPFLAGS +=-std=c++11 #允许使用c++11的函数等功能
APP_CPPFLAGS +=-fpermissive #此项有效时表示宽松的编译形式,比如没有用到的代码中有错误也可以通过编译;使用GNU STL时不用此项std::string 居然编译不通过!!

变量

  • APP_PROJECT_PATH app的根目录,如果当前Application.mk放在$PROJECT/jni/下,则不需要设置此变量
  • APP_MODULES 如果定义了此变量,则只编译指定的模块。模块名使用空格间隔
  • APP_OPTIM release 或 debug ,默认值是release,如果在Android项目中定义了android:debuggable,,则默认是debug
  • APP_CFLAGS 所有模块通用的c编译参数
  • APP_CPPFLAGS 所有模块通用的c++编译参数
  • APP_LDFLAGS 所有模块通用的链接参数
  • APP_BUILD_SCRIPT ndk墨认在jin/下找文件Android.mk,可以使用这个变量指定编译脚本路径。
  • APP_ABI 默认ndk生成的是armeabi,可以使用此变量来指定,例:APP_ABI := armeabi armeabi-v7a x86 mips
  • APP_PLATFORM 指定最小支持android版本,最好不要使用此选项,而是使用模块的build.gradle中的defaultConfig 或 productFlavors 中的minSdkVersion来指定。
  • APP_STL 默认使用系统的std库,可以使用这个指定,可选的见官方文档
  • NDK_TOOLCHAIN_VERSION 指定编译工具的版本,如果是clang就是引用Clang编译器,如果是4.9这种,就是指定GCC的版本
  • APP_SHORT_COMMANDS、APP_PIE、APP_THIN_ARCHIVE 略过,详见文档

gradle配置

如果不用android studio集成编译,而是使用ndk-build手动编译,则不需要配置gradle。

nkd的配置也分散在grale中android节点下面的各个节点中,主要在ndk,defaultConfig.externalNativeBuild.ndkBuildexternalNativeBuild.ndkBuild中,同时在sourceSets中对文件包含进行一些修改,flavor和build-types在不同的编译选项下打不同的包。

必须节点 android.externalNativeBuild

这里至少要包括cmake和ndkbuild结节之一,来表明ndk的编译系统。
然后子结点中,只能设置路径(path参数),只有当Android.mk或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
29
30
31
32
33
android {
...
defaultConfig {...}
buildTypes {...}

// 这里至少要包括cmake和ndkbuild结节之一,来表明ndk的编译系统
externalNativeBuild {
// AndroidStudio2.2以上就添加了Cmake方式来编译NDK代码,但还是可以使用老方法
ndkBuild {

path "src/main/jni/Android.mk"

//path "prebuilts/tinysubstrate-bin/Android.mk"

}

// 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/'
}
}
}

android.defaultConfig.ndk

接口原型

1
2
3
4
5
6
7
8
public interface CoreNdkOptions {
String getModuleName();
String getcFlags();
List<String> getLdLibs();
Set<String> getAbiFilters();
String getStl();
Integer getJobs();
}

注意这里也有cFlags和AbiFilters,这两项和下面的android.defaultConfig.externalNativeBuild重复,原因暂时我还不知道。

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ndk {//在android节点下面
// 生成so的名字,是必须的

moduleName ="JNITest"

toolchain = 'clang'
CFlags.add('-std=c99')

// 添加依赖库
ldLibs.addAll(['android','OpenSLES', 'log'])

// 生成不同abi体系的so库

abiFilters.addAll(['armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64',
'mips', 'mips64'])


}

android.defaultConfig.externalNativeBuild

构建参数,可以在每个Flavor里特殊定义。
文档,接口原型为:

1
2
3
4
5
6
7
public interface CoreExternalNativeNdkBuildOptions {
List<String> getArguments();
List<String> getcFlags();
List<String> getCppFlags();
Set<String> getAbiFilters();
Set<String> getTargets();
}

在gradle里的使用方法见这里

buildTypes

1
2
3
4
5
6
7
8
9
buildTypes {
release {
minifyEnabled = false
proguardFiles.add(file('proguard-rules.pro'))
}
debug {
jniDebuggable true //有这个才会支持调试native 代码,这个放到release里一样能用
}
}