【Android Gradle学习笔记】第八天:NDK的使用

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 的作用是:

  1. 自动调用 NDK 工具链(如编译器 clang、链接器 ld)编译 C/C++ 代码;
  2. 将生成的 .so 库与 Java/Kotlin 代码整合,生成最终的 APK;
  3. 支持配置编译参数(如目标 CPU 架构、C++ 标准版本等)。

AGP 对 NDK 的支持主要依赖两种构建工具:

  • CMake:跨平台构建工具(推荐,AGP 默认集成);
  • ndk-build:NDK 传统构建工具(基于 Makefile,适用于旧项目)。

本文以 CMake 为例,讲解现代 Android 原生开发流程。

安装 NDK 和 CMake

在使用 Gradle 构建原生代码前,需确保开发环境中已安装 NDK 和 CMake:

  1. 打开 Android Studio,进入 File > Settings > Appearance & Behavior > System Settings > Android SDK
  2. 切换到 SDK Tools 标签,勾选:
    • NDK (Side by side):选择一个稳定版本(如 25.2.9519653);
    • CMake:选择与 NDK 兼容的版本(通常默认即可);
  3. 点击「Apply」安装,Android Studio 会自动配置环境变量。

创建支持 NDK 的项目

方式 1:新建项目时直接包含 C++ 支持

  1. 打开 Android Studio,点击「New Project」;
  2. 选择「Native C++」模板(位于「Phone and Tablet」分类下);
  3. 填写项目名称(如「NdkHelloWorld」),选择语言(Java 或 Kotlin),点击「Next」;
  4. 在「Customize C++ Support」页面,可配置:
    • C++ Standard:选择 C++ 标准(如 C++17);
    • Exceptions Support:是否启用 C++ 异常;
    • Runtime Type Information Support:是否启用 RTTI;
  5. 点击「Finish」,Android Studio 会自动生成包含原生代码的项目结构。

方式 2:在现有项目中添加 NDK 支持

  1. 在现有项目的 app/src/main 目录下,创建 cpp 文件夹(用于存放 C/C++ 代码);
  2. cpp 目录下创建 CMakeLists.txt(CMake 构建脚本);
  3. 配置模块级 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'
    // ... 其他依赖
}

核心配置项说明

  1. android.defaultConfig.externalNativeBuild

    配置原生代码的编译参数,如 cppFlags(C++ 编译选项)、abiFilters(目标 CPU 架构)。

    • abiFilters 用于限制生成的 .so 库架构(减少 APK 体积),常见架构:
      • arm64-v8a:64 位 ARM 设备(主流手机);
      • armeabi-v7a:32 位 ARM 设备(旧设备);
      • x86/x86_64:模拟器或 x86 架构设备。
  2. android.externalNativeBuild

    指定 CMake 构建脚本的路径(path)和版本(version),Gradle 会通过该脚本找到原生代码并编译。

  3. 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 调用时找不到函数);
  • JNIEXPORTJNICALL: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 会执行以下操作:

  1. 调用 CMake 根据 CMakeLists.txt 编译 native-lib.cpp,生成对应架构的 .so 库;
  2. .so 库复制到 app/build/intermediates/cmake/debug/obj/ 目录;
  3. 打包 .so 库和 Java/Kotlin 代码到 APK 中。

运行应用

连接 Android 设备或启动模拟器,点击「Run」按钮运行应用。若配置正确,屏幕会显示:

复制代码
Hello from C++

查看生成的 .so 库

编译成功后,可在以下路径找到各架构的 .so 库:

复制代码
app/build/intermediates/cmake/debug/obj/<架构>/libnative-lib.so

总结

  1. 配置解析 :Gradle 读取 build.gradle 中的 externalNativeBuild 配置,定位 CMakeLists.txt
  2. 原生编译 :调用 CMake 和 NDK 工具链(clang)编译 C/C++ 代码,生成 .so 库;
  3. 依赖整合 :将 .so 库与 Java/Kotlin 字节码一起打包到 APK 的 lib/<架构>/ 目录;
  4. 运行时加载 :应用启动时,System.loadLibrary 加载 .so 库,JVM 通过 JNI 调用原生方法。

常见问题与解决方案

  1. 「找不到原生方法」错误

    检查 JNI 函数名是否与 Java/Kotlin 方法名、包名一致(注意下划线和大小写)。

  2. 编译失败:未找到 NDK

    build.gradle 中显式指定 ndkVersion,或在 SDK Tools 中重新安装 NDK。

  3. APK 体积过大

    通过 abiFilters 只保留目标架构(如仅保留 arm64-v8a),减少冗余 .so 库。

  4. C++ 标准不兼容

    cppFlags 中指定正确的标准(如 -std=c++17),并确保 NDK 版本支持该标准。

相关推荐
love530love29 分钟前
【笔记】Intel oneAPI 开发环境配置
人工智能·windows·笔记·oneapi·onednn·deep neural
HansenPole82530 分钟前
元编程笔记
笔记·网络协议·rpc
BoomHe31 分钟前
Android LMK(Low Memory Killer)机制
android
charlie11451419134 分钟前
Git团队协作完全入门指南(上)
笔记·git·学习·教程·工程
迷茫的启明星38 分钟前
Git命令学习
git·学习
时光呀时光慢慢走1 小时前
MAUI 开发安卓 MQTT 客户端:实现远程控制 (完整源码 + 避坑指南)
android·物联网·mqtt·c#
全栈陈序员1 小时前
说说你对 Vue 的理解
前端·javascript·vue.js·学习·前端框架
成都大菠萝1 小时前
2-2-44 快速掌握Kotlin-函数类型操作
android
im_AMBER2 小时前
Leetcode 85 【滑动窗口(不定长)】最多 K 个重复元素的最长子数组
c++·笔记·学习·算法·leetcode·哈希算法