文章

Android NDK 介绍与使用示例

简单介绍

NDK 是干啥的?

全称 Native Development Kit, 允许你在 Android 应用中使用 CC++ 代码

NDK 的好处

  • NDK 可以生成 .so 文件, 方便代码共享
  • 增加反编译的难度
  • 提高性能

使用示例

版本说明

  • Android Studio: 2.2
  • Android Plugin for Gradle: com.android.tools.build:gradle:2.2.0
  • Gradle: 2.14.1

准备工作

在 Android Studio 里下载好 NDK

Android Studio 2.2 稳定版已发布,这个版本增强了 C++ 的开发能力,能够使用 ndk-build 或 CMake 去编译和调试项目里的 C++ 代码

ndk-build 或 CMake 比用 Gradle 去编译 C++ 更好

因为 NDK 套件里自带 ndk-build, 不需要再额外安装 CMake, 为了简单起见以下示例都用 ndk-build 去编译 C++

注意

  • 为了能够在 Android Studio 里使用 CMake 或 ndk-build,你必须使用 Android Studio 2.2 或更高版本 和 Android Plugin for Gradle version 2.2.0 或更高版本
  • 由于 Android Studio 编译 C 和 C++ 代码默认使用 CMake, 因此我建议使用 Experimental Android Plugin for Gradle 的用户也切换到 Android Plugin for Gradle version 2.2.0 或更高版本,这样既可以使用 Android Plugin for Gradle 的稳定版本,也可以使用 CMake 或 ndk-build 来提高编译速度

Android.mk 和 Application.mk

简单来说

  • Android.mk 用来描述需要生成哪些模块的 .so 文件
  • Application.mk 用来描述如何生成 .so 文件,生成静态库还是动态库

这里给出示例文件

Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := gaussianBlur
LOCAL_SRC_FILES := blur.cpp
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)
  • 宏函数 my-dir 是由编译系统提供的,会返回当前目录的路径(当前目录指的是包含 Android.mk 的目录)

  • CLEAR_VARS 这个变量也是由编译系统提供的,会清除很多 LOCAL_XXX 变量

以上两行命令基本上是固定的,不需要去动

  • LOCAL_MODULE 指定模块名称,会自动生成相应的 libgaussianBlur.so 文件

  • LOCAL_SRC_FILES 指定这个模块要编译的 C++ 文件

  • LOCAL_LDLIBS 指定这个模块里会用到哪些原生 API, 详见 Android NDK Native APIs

  • BUILD_SHARED_LIBRARY 根据你之前定义的 LOCAL_XXX 变量,决定要编译啥,如何去编译,这行命令一般也不需要动,固定的

Application.mk

APP_STL := gnustl_static

APP_STL 指定使用哪些 C++ 运行时, 详见 C++ Library Support

Android.mk 和 Application.mk 都放在 jni 目录下 项目文件结构如下

|____app
| |____src
| | |____main
| | | |____jni
| | | | |____Android.mk
| | | | |____Application.mk
| | | | |____blur.cpp

如何使用 C++ 代码?

前面已经给出了 Android.mk 和 Application.mk 的示例,下面在 build.gradle 里配置 externalNativeBuild 就可以自动编译 C++ 代码了

示例内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
defaultConfig {
	applicationId "com.example.app"
	minSdkVersion 16
	targetSdkVersion 24
	versionCode 102
	versionName "0.2"

	externalNativeBuild {
		ndkBuild {
			arguments "NDK_APPLICATION_MK:=src/main/jni/Application.mk"
			cFlags "-DTEST_C_FLAG1", "-DTEST_C_FLAG2"
			cppFlags "-DTEST_CPP_FLAG2", "-DTEST_CPP_FLAG2"
			abiFilters "armeabi-v7a", "armeabi"
		}
	}
}
externalNativeBuild {
	ndkBuild {
		path "src/main/jni/Android.mk"
	}
}
  • path 用来指定 Android.mk 的路径
  • arguments 用来指定 Application.mk 的路径
  • abiFilters 用来指定生成哪些平台的 .so 文件
  • cFlags 和 cppFlags 是用来设置环境变量的, 一般不需要动,和示例一样就好,

好了,现在运行项目,就可以将 blur.cpp 自动编译为 libgaussianBlur.so 文件了

手动生成 .so 文件

如果能直接引用生成好的 .so 文件,可以避免重复编译 .so 文件,从而加快应用 build 速度

下面是手动生成 .so 文件的步骤

1
2
3
4
5
# 进入 main 目录
cd app/src/main

# 生成 .so 文件
/Users/lee/Library/Android/sdk/ndk-bundle/ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=./jni/Application.mk NDK_LIBS_OUT=./jniLibs

执行这个命令后,会在 app/src/main/jniLibs 目录生成各个平台的 .so 文件 如果需要把 .so 文件共享给其他人,把这些平台下的 .so 文件发给其他人就好了

/Users/lee/Library/Android/sdk/ndk-bundle/ndk-build 是我下载好的 NDK 目录路径

  • NDK_PROJECT_PATH 指定项目路径, 会自动读取这个目录下的 jni/Android.mk 文件
  • NDK_APPLICATION_MK 指定 Application.mk 的位置
  • NDK_LIBS_OUT 指定将生成的 .so 文件放到哪个目录,默认 Android Studio 会读取 jniLibs 目录下的 .so 文件, 所以我们把 .so 文件生成到这

测试结果: (测试均在 clean 项目后进行) 引用 .so 文件前平均耗时 1m 27s 引用 .so 文件后平均耗时 47s 我们可以看到 build 速度快了将近一倍

调试 NDK

NDK_LOG 变量为1,就可以打印日志信息

1
ndk-build -e NDK_LOG=1

需要注意的地方

Android Studio 如何知道项目里用了 C 或 C++ 代码并自动去编译它 ?

Android Studio 会检查 app/src/main/jni 目录下是否有 C 或 C++ 代码, 如果有, 就根据 build.gradle 里的配置调用 CMake 或 ndk-build 去编译它

externalNativeBuild 这个我怎么知道如何配 ?

可以参考 Building C++ in Android Studio with CMake or ndk-build

这里有个问题,官方示例里的 targets 参数我不知道怎么用,如果设置为和 Android.mk 里 LOCAL_MODULE 一样的会报错误,不加就能运行

这里有个 bug 报告, Inconsitency between LOCAL_MODULE and gradle target name in ndk-build integration

生成的 .so 文件需要优化么?

参考这个 SO 问题 Android NDK release build

简单来说就是,默认生成的 .so 文件就是已经优化过的,不需要额外做操作了

参考资料

本文由作者按照 CC BY 4.0 进行授权

Comments powered by Disqus.