# JNI 编程上手指南之 HelloWorld 实战 站点:答源(https://www.dandroid.cn/) 原文:https://www.dandroid.cn/archives/20390 Canonical:https://www.dandroid.cn/archives/20390 作者:admin 发布日期:2023-09-15 最后更新:2023-09-15 16:39 分类:JNI / 教程 内容类型:post 说明:本页是原文的纯文本镜像,供大模型与检索器完整读取;不含导航、广告、付费框。引用请以上方「原文」链接为准。 ## 正文 JNI 编程是高级/专家 Android 开发的必备技能之一,接下来我们就一步一步掌握 JNI 编程的方方面面。 1. 基本概念 JNI(Java Native Interface,JAVA 原生接口)。JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 Java 代码能够与用其它编程语言(如 C、C++ 和汇编语言)编写的应用程序和库进行互操作。通俗一点讲就是在 Java 代码里调用 C/C++ 等语言的代码或 C/C++ 代码调用 Java 代码。 JNI 技术在 Android 领域有大量的应用: Java 程序可以通过 JNI 操作硬件 音视频处理,数学运算,实时渲染等领域相关的库基本都使用 C/C++ 编写,我们可以使用 JNI 技术来调用这些库,而不用使用 Java 来重写 相比 C/C++,Java 更容易被反编译,一些和安全相关的代码,我们可以使用 C/C++ 来编写,然后使用 JNI 技术调用 JNI 技术的应用当然不止以上列举的例子,更多的应用方式等待大家的进一步探索。 2. HelloWorld 实战 接下来我们通过一个简单的示例程序,快速地掌握 JNI 的基本使用。 首先编译一个 Java 文件: HelloJNI.java public class HelloJNI { static { System.loadLibrary("hello"); } private native jstring sayHello(); public static void main(String[] args) { new HelloJNI().sayHello(); } } 接着生成 C/C++ 头文件 HelloJNI.h javac -h . HelloJNI.java 该命令会生成一个 HelloJNI.h,这个头文件描述了我们需要实现的函数。 /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class HelloJNI */ #ifndef _Included_HelloJNI #define _Included_HelloJNI #ifdef __cplusplus extern "C" { #endif /* * Class: HelloJNI * Method: sayHello * Signature: ()V */ JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif 生成的函数中有两个参数: JNIEnv:JNIEnv 内部提供了很多函数,方便我们进行 JNI 编程。 C 代码中,JNIEnv 是指向 JNINativeInterface 结构的指针,为了访问任何一个 JNI 函数,该指针需要首先被解引用。因为 C 代码中的 JNI 函数不了解当前的 JNI 环境, JNIEnv 实例应该作为第一个参数传递给每一个 JNI 函数调用调用者,调用格式如下: (*env)->NewStringUTF(env,"Hello from JNI !"); 在 C++ 代码中,JNIEnv 实际上是 C++ 类实例,JNI 函数以成员函数的形式存在,因此 JNI 函数调用不要求 JNIEnv 实例作参数。在 C++ 中,完成同样功能的调用代码格式如下: env->NewstringUTF ( "Hello from JNI ! "); jobject: 指向 "this" 的 Java 对象 如果 java 中的 native 函数是 static 的,那第二个参数是 jclass,代表了 java 中的 Class 类。 extern "C" 告诉 C++ 编译器以 C 的方式来编译这个函数,以方便其他 C 程序链接和访问该函数。C 和 C++ 有着不同的命名协议,因为 C++ 支持函数重载,用了不同的命名协议来处理重载的函数。在 C 中函数是通过函数名来识别的,而在 C++ 中,由于存在函数的重载问题,函数的识别方式通过函数名,函数的返回类型,函数参数列表三者组合来完成的。因此两个相同的函数,经过C,C++编绎后会产生完全不同的名字。所以,如果把一个用 C 编绎器编绎的目标代码和一个用 C++ 编绎器编绎的目标代码进行链接,就会出现链接失败的错误。 JNIEXPORT、JNICALL 两个宏在 linux 平台的定义如下: //该声明的作用是保证在本动态库中声明的方法 , 能够在其他项目中可以被调用 #define JNIEXPORT __attribute__ ((visibility ("default"))) //一个空定义 #define JNICALL 接着我们来实现具体的 C 程序 HelloJNI.c #include "HelloJNI.h" #include #include //方法名要和 Java 层包名对应上 JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) { return (*env)->NewStringUTF(env,"Hello from JNI !"); } 编译和执行(需要配置好 JAVA_HOME 环境变量): gcc -fpic -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so HelloJNI.c java -Djava.library.path=. HelloJNI 至此,一个简单的 demo 就完成了。 3. 动态注册 以上使用 JNI 的方式称为静态注册,还有一种方式叫动态注册,我们接下来看个动态注册的例子吧 java层: com/example/ndk/NativeTest.java package com.example.ndk; public class NativeTest { static { System.loadLibrary("nativetest"); } public native void init(); public native void init(int age); public native boolean init(String name); public native void update(); } C 层的实现主要有三步: 实现 java 层本地方法 构建一个 JNINativeMethod 类型的数组 注册本地函数 NativeTest.c : #include #include #ifdef __cplusplus extern "C" { #endif //1 实现 java 层本地方法 JNIEXPORT void JNICALL c_init1(JNIEnv *env, jobject thiz) { printf("c_init1\n"); } JNIEXPORT void JNICALL c_init2(JNIEnv *env, jobject thiz, jint age) { printf("c_init2\n"); } JNIEXPORT jboolean JNICALL c_init3(JNIEnv *env, jobject thiz, jstring name) { printf("c_init3\n"); } JNIEXPORT void JNICALL c_update(JNIEnv *env, jobject thiz) { printf("c_update\n"); } #ifdef __cplusplus } #endif // typedef struct { // //Java层native方法名称 // const char* name; // //方法签名 // const char* signature; // //native层方法指针 // void* fnPtr; // } JNINativeMethod; //2 构建 JNINativeMethod 数组 //中间的方法签名看上去有点怪异,后面我们来讲它的命名规则 static JNINativeMethod methods[] = { {"init", "()V", (void *)c_init1}, {"init", "(I)V", (void *)c_init2}, {"init", "(Ljava/lang/String;)Z", (void *)c_init3}, {"update", "()V", (void *)c_update}, }; /** * 3 完成动态注册的入口函数 * 其内容基本固定 */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env = NULL; jint result = -1; // 获取JNI env变量 if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { // 失败返回-1 return result; } // 获取native方法所在类 const char* className = "com/example/ndk/NativeTest"; jclass clazz = env->FindClass(className); if (clazz == NULL) { return result; } // 动态注册native方法 if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) { return result; } // 返回成功 result = JNI_VERSION_1_6; return result; } JNINativeMethod 第二个成员变量是方法签名,它的组成规则为: (参数类型标识1参数类型标识2…参数类型标识n)返回值类型标识 其中的类型标识如下图所示: 类型标识 | Java数据类型 | Z | boolean | B | byte | C | char | S | short | I | int | J | long | F | float | D | double | L包名/类名; | 各种引用类型 | V | void | 编译和执行: cd com/example/ndk javac NativeTest.java #回到项目根目录 cd - g++ -fpic -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libnativetest.so NativeTest.c java -Djava.library.path=. com.example.ndk.NativeTest 参考资料 JNI/NDK入门指南之正确姿势了解JNI和NDK JNI 简明教程之手把手教你入门 cross-compiling-for-android-with-the-ndk Android 官方cmake文档 JNI/NDK开发指南 关于 C++ 中的 extern "C" --- 由 答源 DAN 底座 1.4.0 生成 · 2026-11-08T22:20:58+00:00