一文入门 Android NDK 开发

SDK 与 API版本

Android build 中的 Java 版本 - https://developer.android.google.cn/build/jdks?hl=zh_cn
平台代号、版本、API 级别和 NDK 版本 - https://source.android.google.cn/docs/setup/about/build-numbers?hl=zh-cn#platform-code-names-versions-api-levels-and-ndk-releases
维基百科 Android - https://zh.wikipedia.org/wiki/Android

名称 版本号 API等级 发行日期 安全性更新状态
Android 1.0 1.0 1 2008年9月23日 不支持
Android 1.1 1.1 2 2009年2月9日 不支持
Android Cupcake 1.5 3 2009年4月27日 不支持
Android Donut 1.6 4 2009年9月15日 不支持
Android Eclair 2.0 – 2.1 5 – 7 2009年10月26日 不支持
Android Froyo 2.2 – 2.2.3 8 2010年5月20日 不支持
Android Gingerbread 2.3 – 2.3.7 9 – 10 2010年12月6日 不支持
Android Honeycomb 3.0 – 3.2.6 11 – 13 2011年2月22日 不支持
Android Ice Cream Sandwich 4.0 – 4.0.4 14 – 15 2011年10月18日 不支持
Android Jelly Bean 4.1 – 4.3.1 16 – 18 2012年7月9日 不支持
Android KitKat 4.4 – 4.4.4 19 – 20 2013年10月31日 不支持
Android Lollipop 5.0 – 5.1.1 21 – 22 2014年11月12日 不支持
Android Marshmallow 6.0 – 6.0.1 23 2015年10月5日 不支持
Android Nougat 7.0 – 7.1.2 24 – 25 2016年8月22日 不支持
Android Oreo 8.0 – 8.1 26 – 27 2017年8月21日 不支持
Android Pie 9 28 2018年8月6日 不支持
Android 10 10 29 2019年9月3日 不支持
Android 11 11 30 2020年9月8日 不支持
Android 12 12 – 12L 31–32 2021年10月4日 不支持
Android 13 13 33 2022年8月15日 支持
Android 14 14 34 2023年10月4日 支持
Android 15 15 35 2024年10月15日 支持
Android 16 16 36 2025年6月10日 支持
  • 关于ABI ( Application Binary Interface) 说明
ABI 名称 对应 CPU
arm64-v8a 表示第 8 代 64 位 ARM 处理器
armeabi-v7a 表示第 7 代及以上 32 位 ARM 处理器
armeabi 表示第 5 代和第 6 代 32 位 ARM 处理器
x86-64 表示 Intel 64 位处理器(主要平板和虚拟机使用)
x86 表示 Intel 32 位处理器(主要平板和虚拟机使用)

Gradlex SDK 版本配置

https://developer.android.google.cn/ndk/guides/sdk-versions?hl=zh_cn

compileSdkVersion

需要强调的是 修改 compileSdkVersion 不会改变运行时的行为 。当你修改了 compileSdkVersion 的时候,可能会出现新的编译警告、编译错误,但新的 compileSdkVersion 不会被包含到 APK 中:它纯粹只是在编译的时候使用。(你真的应该修复这些警告,他们的出现一定是有原因的)
因此我们强烈推荐 总是使用最新的 SDK 进行编译 。

minSdkVersion

定义应用程序支持的最低API版本

targetSdkVersion

targetSdkVersion 是 Android 提供向前兼容的主要依据
一个targetSdkVersion的属性值表示创建的Android项目使用哪个API版本,一个API版本使用一个整型数字表示,API的全称是Application Programming Interface,即应用程序编程接口,API 19对应的编程接口和API 23定义的编程接口存在差别,因为使用整型数字表示targetSdkVersion的属性值,容易记忆和便于比较它们之间的大小,高版本API编程接口可以兼容低版本API编程接口,反之则不行。

一个 NDK 调用示例

参考 [[N_Cmake]]

CMakeLists.txt

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.

# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)

# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("pathplaning")

# 设置 C++ 标准和相关标志
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -Wall -Wextra")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")


# boost_1_74_0 和 rapidjson 都是包含头文件就能用
set(BOOST_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/boost_1_74_0)
set(RAPIDJSON_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/rapidjson)

# 设置头文件目录
include_directories(
	${BOOST_ROOT}
	${RAPIDJSON_ROOT}
)

# 生成 静态库、动态库或对象库
add_library(${CMAKE_PROJECT_NAME} SHARED
	pathplan/canvas.cpp
	pathplan/dubins.cpp
	pathplan/line.cpp
	pathplan/point.cpp
	pathplan/polygon.cpp
	pathplan/MainWindow.cpp
	android_interface.cpp)
# 链接库和依赖项
target_link_libraries(${CMAKE_PROJECT_NAME}
	android
	log)

主要在

  1. include_directories 设置头文件的目录
  2. add_library 这项目的目标; 生成静态库或动态库, 这里是 SHARED 动态库
  3. target_link_libraries 链接 android 和 log

android_interface.cpp

注意函数名是有规则的 Java_{包名}_{类名}_{函数名}

#include <jni.h>
#include <string>

#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"

#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/multiprecision/number.hpp>

#include "pathplan/MainWindow.h"

extern "C"
JNIEXPORT jstring JNICALL
Java_org_yang_web_action_PathAction_planning(JNIEnv *env, jobject thiz, jstring input) {
    const char* p_input_char = env->GetStringUTFChars(input, nullptr);
    rapidjson::Document doc;
    doc.Parse(p_input_char);
    if(doc.HasParseError()) {
        env->ReleaseStringUTFChars(input, p_input_char);
        return env->NewStringUTF("{\"msg\": \"Parse Error\"}");
    }
    // 如果 MW.path_obs 为空
    rapidjson::Document output;
    //空字符串
    std::string obs_path_area = doc["obs_path_area"].GetString();
    if(obs_path_area.empty() || obs_path_area.size() < 20) {
        //无障碍规划
        output = Single_Machine_Planner_Without_Obstacle_path_planner(doc);
    }else{
        output = Single_Machine_Planner_With_Obstacle_path_planner(doc);
    }
    auto inputString = std::string(p_input_char);
    // 序列化 Document 为字符串
    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    output.Accept(writer);
    std::string outputString = buffer.GetString();
    //
    env->ReleaseStringUTFChars(input, p_input_char);//释放
    std::string message = "planning: input = " + inputString + ", output = " + outputString;
    std::cout << message << std::endl;
    return env->NewStringUTF(outputString.c_str());//返回字符串
}

PathAction.kt

package org.yang.njzj.web.action

import android.content.Intent
import org.yang.njzj.activity.QRCodeActivity
import org.yang.njzj.utils.JacksonUtils
import org.yang.njzj.web.WebActivity
import org.yang.njzj.web.dto.InteractiveMessage
import org.yang.njzj.web.inter.CoroutinesProcess
import org.yang.njzj.web.js.WebInteractiveMediation

import pub.devrel.easypermissions.EasyPermissions
import pub.devrel.easypermissions.PermissionRequest
import java.util.Map

/**
 * @description
 */

class PathAction(private val wim: WebInteractiveMediation): CoroutinesProcess<InteractiveMessage> {

//  区域规划算法模块
    companion object {
        init {
            System.loadLibrary("pathplaning")
        }
    }

    external fun planning(paths: String): String

}

gradle.build.kt

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.devtools.ksp")
}

android {
    namespace = "org.yang.njzj"
    compileSdk = 35
    defaultConfig {
        applicationId = "org.yang.njzj"
        minSdk = 29
        targetSdk = 34
        versionCode = 1
        versionName = "1.3.7"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }
        ndk {
//         ABI 过滤
//            abiFilters.addAll(listOf( "arm64-v8a","armeabi-v7a", "x86_64", "x86"))
            abiFilters.addAll(listOf( "arm64-v8a", "x86_64"))
        }
        externalNativeBuild {
            cmake {
                cppFlags += ""
                arguments += "-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON"
            }
        }
    }
.... 
//    NDK
    externalNativeBuild {
        cmake {
            path = file("src/main/cpp/CMakeLists.txt")
            version = "3.22.1"
        }
    }
}

dependencies {
  
}

CPP 本地调用 Java

Java 定义

// 在 com/example/MyJniHelper.kt 中
class MyJniHelper(private val context: Context) {
    
    // 要调用的实例方法
    fun showToast(message: String) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
    }

    // 要调用的静态方法
    companion object {
        @JvmStatic
        fun logDebug(tag: String, message: String) {
            Log.d(tag, message)
        }
    }
    
    
     // 加载 native 库
    init {
        System.loadLibrary("my-native-lib")
    }

    // 声明 native 方法
    external fun nativeInit()
}


本地代码

本地调用需要有 JNIEnv指针(env)和jobject实例(如果调用实例方法)


// cpp/native-lib.cpp
#include <jni.h>
#include <string>

// 全局引用缓存
jobject gJniHelperInstance = nullptr;
jobject gContext = nullptr;

extern "C" JNIEXPORT void JNICALL
Java_com_example_MyJniHelper_nativeInit(
        JNIEnv* env,
        jobject thiz,   // MyJniHelper 实例
        jobject context // 传入的 Context
) {
    // 缓存实例和 Context
    gJniHelperInstance = env->NewGlobalRef(thiz);
    gContext = env->NewGlobalRef(context);
}

// 调用 Java 方法的辅助函数
void callJavaMethod(JNIEnv* env, const char* message) {
    if (!gJniHelperInstance) return;

    // 1. 获取类引用
    jclass clazz = env->GetObjectClass(gJniHelperInstance);
    
    // 2. 获取方法 ID
    jmethodID method = env->GetMethodID(
        clazz,
        "showToast",
        "(Ljava/lang/String;)V"
    );
    
    // 3. 创建 Java 字符串
    jstring jMessage = env->NewStringUTF(message);
    
    // 4. 调用实例方法
    env->CallVoidMethod(gJniHelperInstance, method, jMessage);
    
    // 5. 清理局部引用
    env->DeleteLocalRef(jMessage);
    env->DeleteLocalRef(clazz);
}

// 调用静态方法
void callStaticJavaMethod(JNIEnv* env) {
    // 1. 获取类引用 (使用完整类路径)
    jclass clazz = env->FindClass("com/example/MyJniHelper");
    
    // 2. 获取静态方法 ID
    jmethodID method = env->GetStaticMethodID(
        clazz,
        "logDebug",
        "(Ljava/lang/String;Ljava/lang/String;)V"
    );
    
    // 3. 创建参数
    jstring tag = env->NewStringUTF("JNI");
    jstring msg = env->NewStringUTF("Called from C++");
    
    // 4. 调用静态方法
    env->CallStaticVoidMethod(clazz, method, tag, msg);
    
    // 5. 清理局部引用
    env->DeleteLocalRef(tag);
    env->DeleteLocalRef(msg);
    env->DeleteLocalRef(clazz);
}


全局引用

关于 gJniHelperInstance = env->NewGlobalRef(thiz); 在 JNI 中,默认传递的参数是​​局部引用​​:

  • 局部引用只在当前 JNI 函数调用期间有效
  • 当 JNI 函数返回时,局部引用会自动失效
  • 如果尝试在后续调用中使用局部引用,会导致崩溃或未定义行为
// 当不再需要时释放全局引用
env->DeleteGlobalRef(gJniHelperInstance);
gJniHelperInstance = nullptr;

调用入口

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val jniHelper = MyJniHelper(this)
        jniHelper.nativeInit() // 初始化 JNI 环境
    }
}

关于 Android.mk

https://developer.android.com/ndk/guides/android_mk?hl=zh-cn

​Android.mk​​ 是 Android 构建系统中用于编译 ​​本地代码(C/C++)​​ 的配置文件。它基于 GNU Make 语法,主要用于定义如何编译 JNI 库(.so文件)、可执行文件或静态库(.a)。

Android.mk 文件位于项目 jni/ 目录的子目录中,用于向构建系统描述源文件和共享库。它实际上是一个微小的 GNU makefile 片段,构建系统会将其解析一次或多次。Android.mk 文件用于定义 Application.mk、构建系统和环境变量所未定义的项目级设置。它还可替换特定模块的项目级设置。

in short 类似 CMakeLists 的一个定义文件 管理编译本地代码库的, 新项目建议优先使用 ​​CMake​​(CMakeLists.txt) 不学也罢

posted @ 2026-01-27 21:27  daidaidaiyu  阅读(19)  评论(0)    收藏  举报