
NDK
NDK(Native Development Kit)是 Android 平台用于开发原生代码(C/C++)的工具集,它允许开发者在 Android 应用中集成高性能的原生模块(如游戏引擎、音视频处理、加密算法等)。
而 Gradle 作为 Android 官方构建系统,通过 Android Gradle Plugin(AGP) 提供了对 NDK 的深度支持,简化了原生代码的编译、打包和集成流程。
本文将从基础概念出发,结合一个完整的「Hello World」示例,详细讲解 Android Gradle 如何支持 NDK 开发,包括环境配置、Gradle 脚本解析、原生代码编写与调用等核心环节。
NDK 与 Gradle 的协作关系
在 Android 项目中,Java/Kotlin 代码运行在 Dalvik/ART 虚拟机上,而原生代码(C/C++)通过编译生成 .so 动态链接库,最终被打包到 APK 中。
Gradle 的作用是:
- 自动调用 NDK 工具链(如编译器
clang
、链接器ld
)编译 C/C++ 代码; - 将生成的 .so 库与 Java/Kotlin 代码整合,生成最终的 APK;
- 支持配置编译参数(如目标 CPU 架构、C++ 标准版本等)。
AGP 对 NDK 的支持主要依赖两种构建工具:
- CMake:跨平台构建工具(推荐,AGP 默认集成);
- ndk-build:NDK 传统构建工具(基于 Makefile,适用于旧项目)。
本文以 CMake 为例,讲解现代 Android 原生开发流程。
安装 NDK 和 CMake
在使用 Gradle 构建原生代码前,需确保开发环境中已安装 NDK 和 CMake:
- 打开 Android Studio,进入 File > Settings > Appearance & Behavior > System Settings > Android SDK;
- 切换到 SDK Tools 标签,勾选:
- NDK (Side by side):选择一个稳定版本(如 25.2.9519653);
- CMake:选择与 NDK 兼容的版本(通常默认即可);
- 点击「Apply」安装,Android Studio 会自动配置环境变量。
创建支持 NDK 的项目
方式 1:新建项目时直接包含 C++ 支持
- 打开 Android Studio,点击「New Project」;
- 选择「Native C++」模板(位于「Phone and Tablet」分类下);
- 填写项目名称(如「NdkHelloWorld」),选择语言(Java 或 Kotlin),点击「Next」;
- 在「Customize C++ Support」页面,可配置:
- C++ Standard:选择 C++ 标准(如 C++17);
- Exceptions Support:是否启用 C++ 异常;
- Runtime Type Information Support:是否启用 RTTI;
- 点击「Finish」,Android Studio 会自动生成包含原生代码的项目结构。
方式 2:在现有项目中添加 NDK 支持
- 在现有项目的 app/src/main 目录下,创建
cpp
文件夹(用于存放 C/C++ 代码); - 在
cpp
目录下创建CMakeLists.txt
(CMake 构建脚本); - 配置模块级
build.gradle
(见下文「Gradle 配置详解」)。
四、项目结构解析:原生代码与配置文件
以「方式 1」创建的项目为例,核心结构如下:
app/
├── src/
│ ├── main/
│ │ ├── java/com/example/ndkhelloworld/ # Java/Kotlin 代码
│ │ │ └── MainActivity.kt # 调用原生方法的入口
│ │ ├── cpp/ # C/C++ 代码
│ │ │ └── native-lib.cpp # 原生实现代码
│ │ └── CMakeLists.txt # CMake 构建脚本
│ └── ...
├── build.gradle # 模块级 Gradle 配置
└── ...
关键文件说明:
native-lib.cpp
:存放 C/C++ 实现代码(如 Hello World 逻辑);CMakeLists.txt
:告诉 CMake 如何编译原生代码(指定源文件、输出库名等);build.gradle
:配置 Gradle 与 NDK 的协作规则(如目标架构、编译参数等)。
连接 Java 与原生代码
模块级 build.gradle
(通常是 app/build.gradle
)是 Gradle 支持 NDK 的核心配置文件,以下是关键配置项解析:
完整配置示例(AGP 7.0+)
groovy
android {
compileSdk 33
defaultConfig {
applicationId "com.example.ndkhelloworld"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
// 配置原生代码编译参数
externalNativeBuild {
cmake {
// C++ 编译选项(如启用 C++17 标准)
cppFlags "-std=c++17"
// 指定生成的 .so 库支持的 CPU 架构(默认生成所有架构)
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
}
}
}
// 配置 CMake 构建脚本路径
externalNativeBuild {
cmake {
path "src/main/CMakeLists.txt" // 指向项目中的 CMakeLists.txt
version "3.22.1" // 指定 CMake 版本(可选)
}
}
// 可选:配置 NDK 版本(若未指定,使用 SDK 中安装的默认版本)
ndkVersion "25.2.9519653"
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
// ... 其他依赖
}
核心配置项说明
-
android.defaultConfig.externalNativeBuild
:配置原生代码的编译参数,如
cppFlags
(C++ 编译选项)、abiFilters
(目标 CPU 架构)。abiFilters
用于限制生成的 .so 库架构(减少 APK 体积),常见架构:arm64-v8a
:64 位 ARM 设备(主流手机);armeabi-v7a
:32 位 ARM 设备(旧设备);x86
/x86_64
:模拟器或 x86 架构设备。
-
android.externalNativeBuild
:指定 CMake 构建脚本的路径(
path
)和版本(version
),Gradle 会通过该脚本找到原生代码并编译。 -
android.ndkVersion
:显式指定 NDK 版本(推荐),避免因默认版本变更导致编译错误。
CMake 构建脚本
CMakeLists.txt
是 CMake 的构建脚本,用于定义如何编译 C/C++ 代码。以下是「Hello World」示例的 CMakeLists.txt
:
完整示例
cmake
# 指定 CMake 最低版本(与 AGP 兼容即可)
cmake_minimum_required(VERSION 3.18.1)
# 声明项目名称(可选,仅用于标识)
project("ndkhelloworld")
# 编译原生库:
# 第一个参数:库名称(如 "native-lib",最终生成 libnative-lib.so)
# 第二个参数:库类型(SHARED 表示动态库,STATIC 表示静态库)
# 后续参数:源文件路径(可多个)
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp
)
# 查找 Android 系统库(如 log 库,用于打印日志)
find_library(
log-lib
log # 对应 Android 的 liblog.so
)
# 链接库:将 native-lib 与 log-lib 链接(如需使用日志功能)
target_link_libraries(
native-lib
${log-lib}
)
核心命令说明
add_library
:定义要编译的原生库,指定库名、类型(动态库SHARED
是 Android 常用类型)和源文件。find_library
:查找系统预编译的库(如log
库用于__android_log_print
打印日志)。target_link_libraries
:将自定义库与系统库链接,确保代码中能调用系统库的函数。
从 C++ 实现到 Java/Kotlin 调用
步骤 1:实现 C++ 原生方法
在 native-lib.cpp
中编写返回「Hello from C++」字符串的函数,需遵循 JNI(Java Native Interface)命名规范:
cpp
#include <jni.h>
#include <string>
// JNI 函数命名规则:Java_包名_类名_方法名(包名中的 "." 替换为 "_")
// 这里对应 Java/Kotlin 中的 "stringFromJNI()" 方法
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkhelloworld_MainActivity_stringFromJNI(
JNIEnv* env, // JNI 环境指针(用于调用 JNI 函数)
jobject /* this */ // 调用该方法的 Java 对象(MainActivity 实例)
) {
std::string hello = "Hello from C++";
// 将 C++ 字符串转换为 Java String 并返回
return env->NewStringUTF(hello.c_str());
}
extern "C"
:确保 C++ 编译器不修改函数名(避免 JNI 调用时找不到函数);JNIEXPORT
和JNICALL
:JNI 关键字,指定函数可被 JVM 调用;jstring
:JNI 类型(对应 Java 的String
);JNIEnv*
:提供 JNI 函数接口(如NewStringUTF
用于创建 Java 字符串)。
步骤 2:在 Java/Kotlin 中声明并调用原生方法
以 Kotlin 为例,在 MainActivity.kt
中声明原生方法并加载 .so 库:
kotlin
package com.example.ndkhelloworld
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
class MainActivity : AppCompatActivity() {
// 声明原生方法(需与 C++ 函数名对应)
external fun stringFromJNI(): String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 调用原生方法并显示结果
val tv: TextView = findViewById(R.id.sample_text)
tv.text = stringFromJNI()
}
// 静态代码块:加载编译生成的原生库(库名与 CMakeLists 中定义的一致)
companion object {
init {
System.loadLibrary("native-lib")
}
}
}
external
:Kotlin 关键字,标识该方法由原生代码实现;System.loadLibrary("native-lib")
:加载libnative-lib.so
库(库名省略前缀lib
)。
构建
构建项目
点击 Android Studio 工具栏的「Sync Project with Gradle Files」同步配置,然后点击「Make Project」(或 Ctrl+F9
)编译项目。
Gradle 会执行以下操作:
- 调用 CMake 根据
CMakeLists.txt
编译native-lib.cpp
,生成对应架构的.so
库; - 将
.so
库复制到app/build/intermediates/cmake/debug/obj/
目录; - 打包
.so
库和 Java/Kotlin 代码到 APK 中。
运行应用
连接 Android 设备或启动模拟器,点击「Run」按钮运行应用。若配置正确,屏幕会显示:
Hello from C++
查看生成的 .so 库
编译成功后,可在以下路径找到各架构的 .so
库:
app/build/intermediates/cmake/debug/obj/<架构>/libnative-lib.so
总结
- 配置解析 :Gradle 读取
build.gradle
中的externalNativeBuild
配置,定位CMakeLists.txt
; - 原生编译 :调用 CMake 和 NDK 工具链(
clang
)编译 C/C++ 代码,生成.so
库; - 依赖整合 :将
.so
库与 Java/Kotlin 字节码一起打包到 APK 的lib/<架构>/
目录; - 运行时加载 :应用启动时,
System.loadLibrary
加载.so
库,JVM 通过 JNI 调用原生方法。
常见问题与解决方案
-
「找不到原生方法」错误 :
检查 JNI 函数名是否与 Java/Kotlin 方法名、包名一致(注意下划线和大小写)。
-
编译失败:未找到 NDK :
在
build.gradle
中显式指定ndkVersion
,或在 SDK Tools 中重新安装 NDK。 -
APK 体积过大 :
通过
abiFilters
只保留目标架构(如仅保留arm64-v8a
),减少冗余 .so 库。 -
C++ 标准不兼容 :
在
cppFlags
中指定正确的标准(如-std=c++17
),并确保 NDK 版本支持该标准。