一文入门 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)
主要在
- include_directories 设置头文件的目录
- add_library 这项目的目标; 生成静态库或动态库, 这里是 SHARED 动态库
- 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) 不学也罢

浙公网安备 33010602011771号