JNI/NDK 基础入门:安卓调用 C/C++ 底层驱动、和嵌入式 Linux 互通数据

📅 2026/7/26 14:02:35
JNI/NDK 基础入门:安卓调用 C/C++ 底层驱动、和嵌入式 Linux 互通数据
JNI/NDK 基础入门安卓调用 C/C 底层驱动、和嵌入式 Linux 互通数据如果说 Android 应用层是白领那 JNI 就是连接工厂车间C/C驱动层的那条传送带。没有它你永远只能隔着玻璃看硬件——今天咱就穿上工服下一趟车间。一、JNI 与 NDK 概念辨析新手最容易被这两个名词绕晕先拆清楚概念全称本质类比JNIJava Native Interface接口规范定义 Java 如何调 C/C插座标准国标NDKNative Development Kit工具包提供编译链和库电工工具箱一句话总结JNI 定规矩NDK 给工具。二、JNI 基本数据类型映射Java 和 C/C 类型不是一一对应的JNI 定义了一套中间类型Java 类型JNI 类型C/C 类型说明booleanjbooleanunsigned char0false, 非0truebytejbytesigned char8位有符号charjcharunsigned short16位 Unicodeshortjshortshort16位intjintint32位longjlonglong long64位这点容易踩坑floatjfloatfloat32位doublejdoubledouble64位Stringjstring需转换不能直接用 char*voidvoidvoid无返回值关键记忆点Java 的long是 64 位的对应 C 的long long不是long64位系统上long可能是 64 位但为了兼容性统一用long long最安全。三、NDK 开发环境搭建3.1 CMake 方式推荐现代化// app/build.gradleandroid{defaultConfig{ndk{abiFiltersarmeabi-v7a,arm64-v8a// 工控设备常用这俩}}externalNativeBuild{cmake{pathsrc/main/cpp/CMakeLists.txtversion3.22.1}}}# CMakeLists.txt cmake_minimum_required(VERSION 3.22.1) project(alspdserial) add_library(${CMAKE_PROJECT_NAME} SHARED serial_port.cpp # 你的 C 源码 ) find_library(log-lib log) target_link_libraries(${CMAKE_PROJECT_NAME} ${log-lib})3.2 ndk-build 方式传统老项目常用# Android.mk LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : alspdserial LOCAL_SRC_FILES : serial_port.cpp LOCAL_LDLIBS : -llog include $(BUILD_SHARED_LIBRARY)四、JNI 函数注册方式4.1 静态注册约定优于配置函数名严格遵循Java_包名_类名_方法名格式// Java 端packagecom.alspd.serial;// 包名publicclassSerialPort{publicstaticnativeintopen(Stringpath,intbaudRate);}// C 端 — 函数名必须匹配externCJNIEXPORT jint JNICALLJava_com_alspd_serial_SerialPort_open(JNIEnv*env,jclass clazz,jstring path,jint baudRate){// ...}缺点函数名太长编译器不会检查匹配错误拼错一个字母就崩溃。4.2 动态注册推荐更安全// 方法映射表staticJNINativeMethod gMethods[]{{open,(Ljava/lang/String;I)I,(void*)native_open},{close,()V,(void*)native_close},{read,([B)I,(void*)native_read},{write,([B)I,(void*)native_write},};// 在 JNI_OnLoad 中注册JNIEXPORT jint JNICALLJNI_OnLoad(JavaVM*vm,void*reserved){JNIEnv*env;if(vm-GetEnv((void**)env,JNI_VERSION_1_6)!JNI_OK){returnJNI_ERR;}jclass clazzenv-FindClass(com/alspd/serial/SerialPort);env-RegisterNatives(clazz,gMethods,sizeof(gMethods)/sizeof(gMethods[0]));returnJNI_VERSION_1_6;}签名规则速查()V— 无参返回 void([B)I— 参数 byte[]返回 int(Ljava/lang/String;I)I— 参数 String int返回 int(II)[B— 参数 int int返回 byte[]五、工控实战JNI 串口读写5.1 Java 声明接口packagecom.alspd.serial;publicclassSerialPortHelper{static{System.loadLibrary(alspdserial);}publicstaticnativeintopen(Stringdevice,intbaudRate);publicstaticnativeintclose();publicstaticnativebyte[]read(intmaxSize);publicstaticnativeintwrite(byte[]data);}5.2 C 核心实现#includejni.h#includefcntl.h#includetermios.h#includeunistd.h#includeandroid/log.h#defineTAGSerialPortJNI#defineLOGD(...)__android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)staticintfd-1;externCJNIEXPORT jint JNICALLJava_com_alspd_serial_SerialPortHelper_open(JNIEnv*env,jclass clazz,jstring device,jint baudRate){constchar*pathenv-GetStringUTFChars(device,nullptr);fdopen(path,O_RDWR|O_NOCTTY|O_NDELAY);if(fd0){LOGD(打开串口失败: %s,path);env-ReleaseStringUTFChars(device,path);return-1;}// 配置串口structtermiosoptions;tcgetattr(fd,options);cfsetispeed(options,B115200);// 波特率cfsetospeed(options,B115200);options.c_cflag|(CLOCAL|CREAD);options.c_cflag~PARENB;// 无校验options.c_cflag~CSTOPB;// 1停止位options.c_cflag~CSIZE;options.c_cflag|CS8;// 8数据位tcsetattr(fd,TCSANOW,options);env-ReleaseStringUTFChars(device,path);returnfd;}externCJNIEXPORT jbyteArray JNICALLJava_com_alspd_serial_SerialPortHelper_read(JNIEnv*env,jclass clazz,jint maxSize){if(fd0)returnnullptr;uint8_tbuf[maxSize];intlenread(fd,buf,maxSize);if(len0)returnnullptr;jbyteArray resultenv-NewByteArray(len);env-SetByteArrayRegion(result,0,len,(jbyte*)buf);returnresult;}六、GPIO 状态读取/sys/class/gpio工控常用 GPIO 控制指示灯、继电器。通过文件系统操作/sys/class/gpio// 导出 GPIOintexportGpio(intpin){intexportFdopen(/sys/class/gpio/export,O_WRONLY);charbuf[4];sprintf(buf,%d,pin);write(exportFd,buf,strlen(buf));close(exportFd);return0;}// 读取 GPIO 值intreadGpioValue(intpin){charpath[64];sprintf(path,/sys/class/gpio/gpio%d/value,pin);intfdopen(path,O_RDONLY);charvalue;read(fd,value,1);close(fd);returnvalue1?1:0;}七、与嵌入式 Linux 互通数据工控场景中Android 板常需要跟同设备上的 Linux 系统通信互通方式适用场景优缺点共享文件低频数据交换简单无实时性Unix Domain Socket高频本地通信低延迟适合进程间串口直连Android ↔ MCU工控经典方案TCP Socket跨设备/跨系统需要网络栈共享内存 (ASHMEM)大量数据性能最优复杂度高八、常见 JNI 开发坑点内存泄漏GetStringUTFChars()必须配对ReleaseStringUTFChars()NewLocalRef()太多会超限用DeleteLocalRef()释放线程安全JNIEnv 线程绑定——不能在 A 线程获取的 env 给 B 线程用需用JavaVM::AttachCurrentThread()异常检查Native 调用 Java 方法后必须ExceptionCheck()否则异常会延迟崩溃native 方法声明问题忘记extern C会导致 C name mangling函数找不到so 库加载失败检查 abiFilters 是否匹配、库名是否正确libalspdserial.so加载时写alspdserial// 标准安全检查写法env-CallVoidMethod(obj,methodId);if(env-ExceptionCheck()){env-ExceptionDescribe();env-ExceptionClear();return-1;}JNI 的本质是把 Android 从一个应用系统降维成一台能直接跟硬件掐架的 Linux 设备。会了这招工控开发才算真正落地。