2、开发环境搭建与第一个NNAPI应用

说实话,每次开始一个新项目,最磨人的往往不是算法本身,而是环境搭建。我记得刚接触NNAPI那会儿,光是NDK和CMake的配置就折腾了大半天。今天咱们就把这些基础工作一次性搞定,然后跑起第一个模型——一个简单的加法模型。

本章核心目标:

  • 配置Android Studio的NDK与CMake环境
  • 创建第一个支持NNAPI的Android项目
  • 加载并运行一个简单的加法模型
开发环境搭建与第一个NNAPI应用 · 知识体系 ① 环境准备 NDK + CMake + SDK ② 创建项目 C++支持 + CMakeLists.txt ③ 模型加载 NNAPI推理 安装NDK & CMake 配置CMakeLists.txt 编写Native代码 最终产出 一个能运行加法模型的Android App 输入两个数 → NNAPI推理 → 输出相加结果

2.1 配置NDK与CMake

Android Studio从3.2版本开始,对NDK的支持已经很完善了。我个人习惯用SDK Manager来安装,这样版本不容易搞混。

安装步骤:

  1. 打开Android Studio,进入 SDK Manager → SDK Tools
  2. 勾选 NDK (Side by side)CMake
  3. 点击Apply,等待下载完成

💡 我的小建议:NDK版本建议选21.x以上,对NNAPI的支持更稳定。我之前用NDK 18踩过坑,有些API不支持,折腾了半天才发现是版本问题。

安装完成后,在项目的 local.properties 文件中,会自动生成NDK路径。你也可以手动指定:

ndk.dir=/Users/你的用户名/Library/Android/sdk/ndk/21.4.7075529
cmake.dir=/Users/你的用户名/Library/Android/sdk/cmake/3.18.1

2.2 创建第一个NNAPI项目

新建项目时,记得勾选 Include C++ support。这个选项在Android Studio的向导里,别漏了。

项目创建后,你会看到 app/src/main/cpp 目录,里面有个 CMakeLists.txt 文件。嗯,这里就是咱们的主战场。

CMakeLists.txt 配置示例:

cmake_minimum_required(VERSION 3.18.1)
project("nnapi_demo")

add_library(
    nnapi_demo
    SHARED
    src/main/cpp/nnapi_bridge.cpp
)

find_library(
    log-lib
    log
)

target_link_libraries(
    nnapi_demo
    ${log-lib}
    android
    -lneuralnetworks
)

⚠️ 注意:链接 -lneuralnetworks 这一步很关键。我刚开始做的时候忘了加,结果编译通过但运行时一直报 UnsatisfiedLinkError,查了半天才发现是链接库没加。

2.3 编写Native代码:加载加法模型

咱们先做一个最简单的加法模型。说白了,就是输入两个数,输出它们的和。虽然简单,但NNAPI的完整流程一个不少。

nnapi_bridge.cpp 核心代码:

#include <jni.h>
#include <android/neuralnetworks.h>
#include <android/log.h>

#define LOG_TAG "NNAPI_Demo"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

extern "C" JNIEXPORT jfloat JNICALL
Java_com_example_nnapidemo_MainActivity_addWithNNAPI(
    JNIEnv* env, jobject thiz, jfloat a, jfloat b) {

    // 1. 创建ANeuralNetworksMemory
    ANeuralNetworksMemory* memory = nullptr;
    float input_data[2] = {a, b};
    float output_data[1] = {0};

    // 2. 创建模型
    ANeuralNetworksModel* model = nullptr;
    ANeuralNetworksModel_create(&model);

    // 3. 定义操作数类型(两个float输入,一个float输出)
    ANeuralNetworksOperandType float32Type;
    float32Type.type = ANEURALNETWORKS_TENSOR_FLOAT32;
    float32Type.scale = 0.0f;
    float32Type.zeroPoint = 0;
    uint32_t input_dimensions[1] = {1};
    float32Type.dimensions = input_dimensions;
    float32Type.dimensionCount = 1;

    // 4. 添加操作数
    int32_t input1_idx, input2_idx, output_idx;
    ANeuralNetworksModel_addOperand(model, &float32Type);
    ANeuralNetworksModel_addOperand(model, &float32Type);
    ANeuralNetworksModel_addOperand(model, &float32Type);

    // 5. 添加加法操作(ANEURALNETWORKS_ADD)
    ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD, 3,
        new uint32_t[3]{0, 1, 2}, 1, new uint32_t[1]{2});

    // 6. 标识输入输出
    ANeuralNetworksModel_identifyInputsAndOutputs(model, 2,
        new uint32_t[2]{0, 1}, 1, new uint32_t[1]{2});

    // 7. 编译模型
    ANeuralNetworksCompilation* compilation = nullptr;
    ANeuralNetworksCompilation_create(model, &compilation);
    ANeuralNetworksCompilation_finish(compilation);

    // 8. 创建执行实例
    ANeuralNetworksExecution* execution = nullptr;
    ANeuralNetworksExecution_create(compilation, &execution);

    // 9. 设置输入数据
    ANeuralNetworksExecution_setInput(execution, 0, nullptr, &float32Type,
        input_data, sizeof(float));
    ANeuralNetworksExecution_setInput(execution, 1, nullptr, &float32Type,
        input_data + 1, sizeof(float));

    // 10. 设置输出数据
    ANeuralNetworksExecution_setOutput(execution, 0, nullptr, &float32Type,
        output_data, sizeof(float));

    // 11. 执行推理
    ANeuralNetworksExecution_startCompute(execution, nullptr);

    // 12. 释放资源
    ANeuralNetworksExecution_free(execution);
    ANeuralNetworksCompilation_free(compilation);
    ANeuralNetworksModel_free(model);

    return output_data[0];
}

🔑 关键点解析:

  • ANEURALNETWORKS_ADD 是NNAPI内置的加法操作,不需要训练模型
  • 操作数索引从0开始,输入0、输入1、输出2
  • 执行推理时,第二个参数传 nullptr 表示同步执行

2.4 Java层调用

MainActivity.java 中,加载Native库并调用:

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("nnapi_demo");
    }

    private native float addWithNNAPI(float a, float b);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        float result = addWithNNAPI(3.5f, 2.7f);
        Log.d("NNAPI_Demo", "3.5 + 2.7 = " + result);
        // 输出:3.5 + 2.7 = 6.2
    }
}

💡 避坑指南:我曾经在真机上测试时,发现某些老款手机不支持NNAPI。建议在运行前检查设备兼容性:

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
    // 可以安全使用NNAPI
} else {
    // 回退到CPU计算
}

2.5 运行与调试

连接真机或启动模拟器(API 28+),点击Run。你会在Logcat中看到输出结果。

常见问题排查:

问题 原因 解决方案
UnsatisfiedLinkError Native库未正确加载 检查CMakeLists.txt中的库名和Java中的loadLibrary是否一致
ANEURALNETWORKS_BAD_DATA 操作数类型或维度不匹配 检查float32Type的dimensionCount和dimensions设置
ANEURALNETWORKS_OP_FAILED 设备不支持该操作 使用ANEURALNETWORKS_PREFER_LOW_POWER或回退到CPU

说实话,第一次跑通NNAPI的时候,虽然只是个加法,但那种成就感还是很实在的。你想想看,从底层硬件加速到上层应用,整个链路都打通了。后面咱们就可以在这个基础上,加载真正的AI模型了。

📌 本章小结:

  • NDK和CMake是NNAPI开发的基础,版本要匹配
  • NNAPI的API流程:创建模型 → 添加操作数 → 添加操作 → 编译 → 执行
  • 加法模型虽然简单,但涵盖了NNAPI的完整生命周期
  • 真机调试时注意API版本和硬件兼容性

公众号:蓝海资料掘金营,微信deep3321