在混合编程场景中,将C++实现的核心算法或底层功能封装为so共享库,再通过Java的native方法调用,是JNI开发里的典型做法。这种方式既能复用成熟的C++代码,又能保留Java在业务层的开发效率。

一、准备开发与编译环境
要进行C++生成so库并调用native方法,首先得准备好基础工具链。在Linux系统上,通常需要安装gcc或者g++编译器,以及JDK开发包,因为JNI头文件来自于JDK。如果是Android平台,则需要配置NDK环境,用ndk-build或CMake来完成交叉编译。
确认环境时,可以在终端输入g++ --version和javac -version查看是否安装成功。JDK安装目录下有个include文件夹,里面的jni.h以及不同系统的jni_md.h定义了所有JNI相关的类型和宏,编译C++代码时必须把这两个路径加到头文件搜索目录中,否则会出现找不到jni.h的错误。
二、编写Java侧的native方法声明
Java代码里用native关键字标记的方法,表示它的实现不在Java中,而是由外部动态库提供。例如定义一个简单类,声明一个求两个整数和的native方法,这一步只是给出方法签名,并不需要方法体。
写完Java类之后,需要使用javac编译成class文件,再执行javah -jni 类名命令生成对应的C++头文件。这个头文件里会自动生成一个符合JNI命名规范的函数声明,类似Java_包名_类名_方法名。开发者在C++中要实现的就是这个函数,函数参数里包含JNIEnv指针和jobject对象,以及Java传过来的基本类型参数。
Java示例代码说明
假设包名为com.demo,类名为NativeLib,里面写了public native int add(int a, int b);。经过javah处理,头文件中的函数名会变成Java_com_demo_NativeLib_add,参数列表为JNIEnv *env, jobject obj, jint a, jint b。注意jint对应C++里的整型,但它是JNI定义的有符号类型,直接当int用一般没问题。
这种映射关系是JNI固定规则,包名中的点换成下划线,方法名原样保留。如果Java方法名改动,头文件也要重新生成,否则运行时会报UnsatisfiedLinkError,因为虚拟机按命名规则去找函数却找不到。
三、用C++实现并编译为so库
新建一个cpp文件,包含前面生成的头文件,然后写出函数体。比如把两个jint相加后返回结果。实现时注意所有JNI类型不要混用错,返回值也要转成对应j类型。
编译命令在Linux下通常是:g++ -shared -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux native_lib.cpp -o libnative.so。其中-shared表示生成共享库,-fPIC产生位置无关代码,这是so库必需的特性。-I后面接JNI头文件路径,输出文件名一般以lib开头、.so结尾,因为Java加载库时会自动补上前缀和后缀。
Android平台差异
在Android上,一般不用手动敲g++命令,而是写Android.mk或CMakeLists.txt。CMake里要设置add_library为SHARED,并链接log库方便调试。编译产物同样要是libxxx.so,放进jniLibs对应架构目录,Gradle打包时才会一并打入APK。
无论哪种平台,核心思路一致:按JNI规范实现函数、编成位置无关的共享对象、命名满足lib加库名加.so的形式。只要任何一环不对,后续Java加载就会失败。
四、Java中加载so并调用native方法
在Java类里,通常用静态代码块调用System.loadLibrary("native")来加载库,括号里填去掉lib前缀和.so后缀的库名。虚拟机启动后会去java.library.path指定的目录寻找这个库,也可以改用System.load("绝对路径/libnative.so")直接指定文件位置,避开路径配置麻烦。
加载完成后,就能像普通Java方法一样调用native方法了。第一次调用前务必保证库已加载,否则直接调用会抛异常。下面用表格列出两种加载方式区别:
| 加载方式 | 参数内容 | 适用情况 |
|---|---|---|
| System.loadLibrary | 库名不含前缀后缀 | 库已放在系统或项目库路径中 |
| System.load | so文件的完整绝对路径 | 路径不固定或调试阶段直接指定 |
常见错误与排查
如果报java.lang.UnsatisfiedLinkError,优先检查so文件名是否匹配、是否放对目录、以及C++里函数名是否和头文件完全一致。还有一种是架构不匹配,比如Linux上是x86_64的so,却想在aarch64环境加载,这种也会直接失败。
另一个易错点是JNIEnv的使用。在C++里JNIEnv是指针的指针,调用函数要用env->FindClass形式,而C语言里要用(*env)->FindClass。混用写法会导致编译错误,写代码时看清自己是cpp文件还是c文件。
五、完整调用链路回顾
整体流程可以归纳为:写带native的Java类、生成JNI头文件、用C++实现函数、编译成libxxx.so、Java侧loadLibrary后调用。这条链路在后端服务做高性能模块,或在Android做音视频、加密等底层处理时都非常普遍。
实际项目中,还可以把多个native方法放在同一个so里,也可以由so反向调用Java方法实现回调,那就是进阶的JNI内容了。但把最基础的编译和调用跑通,是后续所有复杂操作的前提,建议多在本地环境演练几遍以熟悉各环节的报错信息。
C++_so库 native方法调用 JNI开发修改时间:2026-08-11 15:09:32