【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 版本支持该标准。

相关推荐
袁震2 小时前
Android-Compose 列表组件详解
android·recyclerview·compose
摇滚侠3 小时前
Spring Boot 3零基础教程,WEB 开发 自定义静态资源目录 笔记31
spring boot·笔记·后端·spring
摇滚侠3 小时前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 遍历 笔记40
spring boot·笔记·thymeleaf
Jose_lz4 小时前
C#开发学习杂笔(更新中)
开发语言·学习·c#
Chloeis Syntax4 小时前
接10月12日---队列笔记
java·数据结构·笔记·队列
2501_916007474 小时前
提升 iOS 26 系统流畅度的实战指南,多工具组合监控
android·macos·ios·小程序·uni-app·cocoa·iphone
QT 小鲜肉4 小时前
【个人成长笔记】Qt 中 SkipEmptyParts 编译错误解决方案及版本兼容性指南
数据库·c++·笔记·qt·学习·学习方法
A9better4 小时前
嵌入式开发学习日志41——stm32之SPI总线基本结构
stm32·单片机·嵌入式硬件·学习
zh_xuan4 小时前
android 利用反射和注解绑定控件id和点击事件
android·注解·反射·控件绑定