一、NDK 配置
开发环境
MacOS 10.15.3
AndroidStudio 3.6.2
准备
从 SDK Manager - SDK Tools 下载 LLDB、CMake 和 NDK。
下载后如果为方便开发可以配置 ndk 的环境变量,也可以不配置。
Hello World
1.声明 Native 方法
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("helloJni");
}
public native String get();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.tv);
tv.setText(get());
}
}
2.实现 Native 方法
1.创建 jni 文件夹。
main 目录右键 -- New -- Folder -- JNI Folder
2.jni 下创建 3 个文件 hello.cpp、Android.mk、Application.mk
hello.cpp
#include <jni.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
// 替换为你项目的包名
jstring Java_com_dixon_ndkdemo_MainActivity_get(JNIEnv *env, jobject thiz) {
printf("invoke get in c++\n");
return env->NewStringUTF("Hello from JNI in helloJni.so !");
}
#ifdef __cplusplus
}
#endif
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := helloJni
LOCAL_SRC_FILES := hello.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_ABI := all
3.编译 so 库
1.工程 build.gradle 添加下载的 ndk 版本。
android {
...
defaultConfig {
...
// 你的ndk版本
ndkVersion "21.0.6113669"
...
2.Terminal 切换到 jni 目录,执行 ndk-build 命令。因为我没有配置环境变量,所以是如下调用的:
~/Library/Android/sdk/ndk/21.0.6113669/ndk-build
ndk 编译完成,main 目录下生成 libs 目录,libs 文件夹内容如下。

4.关联 Gradle
模块右键 -- Link C++ Project with Gradle,弹出的下拉菜单中选择 ndk-build,Project Path 选择 Android.mk 脚本文件。
此操作将在模块 build.gradle 中生成如下代码:
android {
...
externalNativeBuild {
ndkBuild {
path file('src/main/jni/Android.mk')
}
}
}
完成配置,编译运行即可。
二、ndk-build 详解
使用 NDK 编译项目共有三种方式。
- 基于 Make 的 ndk-build;(上文即是,本文将重点解析这种方式)
- CMake
- 独立工具链
本文重点讲解上例中的 ndk-build 编译方式。
ndk-build 可以分为三个文件,分别是 ndk-build 脚本、Android.mk、Application.mk 文件,下面一一说明。
ndk-build 脚本
ndk-build 脚本使用 NDK 的基于 Make 的编译系统 构建项目。
使用 ndk-build <option>
执行特殊命令,其中 <option> 包含如下几种参数:
option | mean |
---|---|
V=1 | 启动编译,并显示编译命令 |
-B | 强制执行完整的重新编译 |
-C <project> | 编译位于 <project> 的项目路径的原生代码。如果不想在终端通过 cd 切换到该路径,此选项会非常有用 |
NDK_APPLICATION_MK=<file> | 使用 NDK_APPLICATION_MK 变量指向的特定 Application.mk 文件进行编译 |
NDK_DEBUG=1 | 强制执行可调试的编译 |
NDK_DEBUG=0 | 强制执行发布版编译 |
APP_ABI="armeabi armeabi-v7a x86" | 编译多平台动态库 |
TARGET_PLATFORM=android-21 | 指定sdk版本 |
Android.mk
1. 概述
Android.mk 文件用于向编译系统描述源文件和共享库。
Android.mk 文件用于定义 Application.mk、编译系统和环境变量所未定义的项目范围设置。它还可替换特定模块的项目范围设置。
2. 基础知识
本部分将解释 Hello world 例子中 Android.mk 文件每一行的作用(语法)。
<1.必须先 定义 LOCAL_PATH
变量:
LOCAL_PATH := $(call my-dir)
此变量表示源文件的路径。在这行代码中,编译系统提供的宏函数 my-dir 将返回当前目录(Android.mk 文件所在目录)的路径。
声明路径
<2.下一行 声明 CLEAR_VARS
变量,其值由编译系统提供:
include $(CLEAR_VARS)
该变量用于清除许多 LOCAL_XXX
变量,如 LOCAL_MODULE
、LOCAL_SRC_FILES
和 LOCAL_STATIC_LIBRARIES
等,但是不会清除 LOCAL_PATH
。在描述每个模块之前,必须重新声明此变量。
清空变量
清空变量的原因是,NDK 可能会多次解析 Android.mk 文件,每次使用其中某些变量的不同定义,所以需要清空变量然后按需重定义。
<3.定义 LOCAL_MODULE
,该变量存储要编译的模块的名称,应用的每个模块仅能使用一次。
LOCAL_MODULE := helloJni
上面的例子会生成 libhelloJni.so 库。
每个模块名称必须唯一,且不含任何空格。
定义 so 库唯一名称
<4.定义 LOCAL_SRC_FILES
, 该变量必须包含要编译到模块中的 C 或 C++ 源文件列表。
LOCAL_SRC_FILES := hello.cpp myfun.cpp
上面的例子会把 hello.cpp、myfun.cpp 中的代码都编译到同一个 so 库中。多个文件以空格分割。
定义源文件列表
<5.声明 BUILD_SHARED_LIBRARY
,该变量帮助系统将所有内容连接到一起。
include $(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY
变量指向一个 GNU Makefile 脚本,该脚本会收集您自最近 include 以来在 LOCAL_XXX
变量中定义的所有信息。此脚本确定要编译的内容以及编译方式。
收集变量
3. 变量和宏
编译系统提供了许多可在 Android.mk
文件中使用的变量。其中许多变量已预先赋值,另一些变量需要自己赋值。
除了这些变量之外,还可以自己定义任意变量。在定义变量时请注意,NDK 编译系统保留了下列变量名称:
以 LOCAL_
开头的名称,例如 LOCAL_MODULE
。
以 PRIVATE_
、NDK_
或 APP
开头的名称。编译系统在内部使用这些变量名。
小写名称,例如 my-dir
。编译系统也是在内部使用这些变量名。
为了方便 需要在 Android.mk
文件中定义自己的变量,建议在名称前附加 MY_
。
4. include 变量
NDK 定义了一些 include 变量,使用语法为 include $(CLEAR_VARS)
。
清除变量。
收集变量,并确认如何根据源文件编译目标可执行文件。
收集变量,并确认如何根据源文件编译目标共享库。
其它变量详见 官方文档-NDK 定义的 include 变量。
5. 目标信息变量
编译系统解析 Android.mk 文件时面向的 CPU 系列,此变量是 arm、arm64、x86 或 x86_64 之一。
示例用法:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)
LOCAL_MODULE := helloJni
LOCAL_SRC_FILES := hello.cpp myfun.cpp
endif
include $(BUILD_SHARED_LIBRARY)
当编译命令为 ~/Library/Android/sdk/ndk/21.0.6113669/ndk-build TARGET_ARCH_ABI=armeabi-v7a
,编译通过,libs 下仅生成 ameabi-v7a
目录(目录下生成 libhelloJni.so
)。
编译系统解析 Android.mk 文件时面向的 Android API。
ifeq ($(TARGET_PLATFORM),android-22)
# ... do something ...
endif
ndk-build TARGET_PLATFORM=android-22
编译系统解析 Android.mk 文件时面向的 ABI。
ABI 和 CPU 架构 的关系如下:
CPU 架构 | 设置 |
---|---|
ARMv7 | armeabi-v7a |
ARMv8 AArch64 | arm64-v8a |
i686 | x86 |
x86-64 | x86_64 |
ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
# ... do something ...
endif
其它目标信息变量详见 官方文档-目标信息变量。
6.模块描述变量
每个模块描述都应遵守以下基本流程:
- 使用 CLEAR_VARS 变量初始化或取消定义与模块相关的变量。
- 为用于描述模块的变量赋值(LOCAL_XXX)。
- 使用 BUILD_XXX 变量设置 NDK 编译系统,使其将适当的编译脚本用于该模块。
模块描述变量除了上述提到的 LOCAL_PATH
,LOCAL_MODULE
,LOCAL_SRC_FILES
外还有很多,可以查询 官方文档-模块描述变量,按需调用。
7.函数宏
使用 $(call <function>)
可以对这些宏进行求值;它们将返回文本信息。
1.my-dir
这个宏返回最后包含的 makefile 的路径,通常是当前 Android.mk 的目录。my-dir 可用于在 Android.mk 文件的开头定义 LOCAL_PATH。例如:
LOCAL_PATH := $(call my-dir)
函数宏不是本文重点,更多函数宏参考 官方文档-函数宏。
Application.mk
Application.mk 用于指定 ndk-build 的项目范围设置。
1.APP_ABI
不同的 Android 设备使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 与指令集的每种组合都有专属的应用二进制接口 (ABI)。
APP_ABI 用于生成不同指令集下的动态库。他们的对应关系如下:
指令集 | 命令 |
---|---|
32 位 ARMv7 | APP_ABI := armeabi-v7a |
64 位 ARMv8 (AArch64) | APP_ABI := arm64-v8a |
x86 | APP_ABI := x86 |
x86-64 | APP_ABI := x86_64 |
所有支持的 ABI(默认) | APP_ABI := all |
多个值使用空格分割:
APP_ABI := armeabi-v7a arm64-v8a x86
2.APP_BUILD_SCRIPT
默认情况下,ndk-build 假定 Android.mk 文件位于项目根目录的相对路径 jni/Android.mk 中。
要从其他位置加载 Android.mk 文件,可将 APP_BUILD_SCRIPT 设置为 Android.mk 文件的绝对路径。
3.APP_PROJECT_PATH
项目根目录的绝对路径。
更多变量参考 官方文档-Application.mk 变量
三、小结
本文主要讲解了如何配置简单的 NDK 项目,以及配置所用到的语法说明,后续将继续更新。
[TOC]
网友评论