如何通过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) 不学也罢