如何通过Android NDK进行高效本地开发?
摘要:SDK 与 API版本 Android build 中的 Java 版本 - https:developer.android.google.cnbuildjdks?hl=zh_cn 平台代号、版本、API 级别和 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) 不学也罢
