这个过程被称为 JNI (Java Native Interface),即 Java 本地接口,它是一套编程框架,允许 Java 代码和其他语言(如 C/C++)进行互相调用。

下面我将详细解释整个流程,从环境搭建到具体的调用方法,并提供一个完整的示例。
核心概念与流程
JNI 的本质:C/C++ 代码本身不能直接“理解”或“执行”Java 代码,JNI 提供了一组 API,让 C/C++ 代码能够:
- 获取 JVM (Java Virtual Machine) 的实例:这是所有 Java 操作的入口。
- 通过 JVM 找到目标 Java 类:通常是通过类的全限定名(如
java/lang/String)。 - 获取类的 ID (Class ID):这是一个指向类定义的句柄。
- 获取方法或字段的 ID:通过方法名、签名和类 ID,找到具体的 Java 方法或字段。
- 调用方法或访问字段:使用上一步获取的 ID,以及 JNI 提供的函数(如
CallVoidMethod,GetIntField)来执行操作。
调用流程图:
[C/C++ Code]
|
| 1. 获取 JVM
V
[JVM (Java Virtual Machine)]
|
| 2. 查找类
V
[Java Class (e.g., com/example/MyClass)]
|
| 3. 查找方法
V
[Java Method (e.g., myJavaMethod)]
|
| 4. 调用方法
V
[Execute Java Code]
详细步骤与代码示例
我们将创建一个简单的示例,

- 一个 Java 类 (
MyJavaClass) 有一个静态方法和一个实例方法。 - C 代码会调用这个 Java 类的静态方法来打印日志,然后创建该类的实例,并调用其实例方法来获取一个字符串,最后将这个字符串返回给 C 代码并打印。
第 1 步:创建 Java 类并声明 native 方法
在 Java 代码中定义一个类,并声明那些需要由 C/C++ 实现的方法,这些方法必须使用 native 关键字。
app/src/main/java/com/example/jnidemo/MyJavaClass.java
package com.example.jnidemo;
public class MyJavaClass {
// 静态方法,由 C 调用
public static void callStaticMethod() {
System.out.println("Hello from Java's static method!");
}
// 实例方法,由 C 调用
public String callInstanceMethod(String input) {
System.out.println("Java's instance method received: " + input);
return "Processed by Java: " + input;
}
// C 代码会调用这个方法来开始整个流程
public static void startJNICall() {
System.loadLibrary("jnidemo"); // 加载 C/C++ 编译生成的 .so 库
NativeBridge nativeBridge = new NativeBridge();
nativeBridge.callJavaMethodsFromC();
}
}
第 2 步:创建 JNI 入口点类
这个类是 C/C++ 代码与 Java 代码之间的桥梁,它的方法会由 C/C++ 代码调用。
app/src/main/java/com/example/jnidemo/NativeBridge.java

package com.example.jnidemo;
public class NativeBridge {
// 声明一个 native 方法,这个方法在 C 中实现
// 这个方法将作为我们 C 代码的入口点
public native void callJavaMethodsFromC();
}
第 3 步:生成 JNI 头文件
这是最关键的一步,它定义了 C/C++ 函数的签名,以便与 Java 的 native 方法对应。
-
编译 Java 代码:确保你的 Java 代码已经编译成功,Android Studio 通常会自动处理。
-
使用
javac和javah(或javac+javap):-
旧方法 (
javah):javah工具会根据.class文件生成.h头文件。# 1. 编译 Java 文件 javac -d . MyJavaClass.java NativeBridge.java # 2. 使用 javah 生成头文件 # -jni: 生成 JNI 风格的头文件 # -classpath: 指定 .class 文件所在目录 # -o: 指定输出文件名 javah -jni -classpath . -o native_bridge.h com.example.jnidemo.NativeBridge
-
新方法 (
javap):javap是 Java 自带的反汇编工具,更现代,你可以用它来查看方法的签名。javap -s -p -cp . com.example.jnidemo.NativeBridge
你会看到类似
(Lcom/example/jnidemo/NativeBridge;)V这样的签名,这对应了 C 函数的参数和返回类型。
生成的
native_bridge.h内容会是这样:/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_jnidemo_NativeBridge */ #ifndef _Included_com_example_jnidemo_NativeBridge #define _Included_com_example_jnidemo_NativeBridge #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_jnidemo_NativeBridge * Method: callJavaMethodsFromC * Signature: ()V */ JNIEXPORT void JNICALL Java_com_example_jnidemo_NativeBridge_callJavaMethodsFromC (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endifJNIEXPORT,JNICALL: JNI 规定的宏,用于导出函数。Java_包名_类名_方法名: C 函数的命名规则。(JNIEnv *, jobject): 前两个固定参数。JNIEnv是 JNI 接口指针,jobject是调用该方法的 Java 对象实例(对于静态方法,是jclass)。()V: 方法的签名。()V表示无参数,无返回值 (Void)。
-
第 4 步:编写 C/C++ 实现代码
我们来实现 native_bridge.h 中声明的函数。
app/src/main/cpp/native_bridge.cpp (推荐使用 C++ 以便更好地管理资源)
#include <jni.h>
#include <string>
#include <android/log.h> // 用于在 logcat 中打印日志
#define LOG_TAG "JNI_DEMO"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
extern "C" JNIEXPORT void JNICALL
Java_com_example_jnidemo_NativeBridge_callJavaMethodsFromC(
JNIEnv* env,
jobject /* this */) { // 对于实例方法,这个参数是调用该方法的 Java 对象
// 1. --- 调用 Java 静态方法 ---
// a. 找到 Java 类
jclass clazz = env->FindClass("com/example/jnidemo/MyJavaClass");
if (clazz == nullptr) {
LOGE("Failed to find class MyJavaClass");
return;
}
// b. 获取静态方法的 ID
// 方法签名: ()V -> 无参数,无返回值
jmethodID staticMethodId = env->GetStaticMethodID(clazz, "callStaticMethod", "()V");
if (staticMethodId == nullptr) {
LOGE("Failed to find static method callStaticMethod");
return;
}
// c. 调用静态方法
LOGI("Calling Java static method...");
env->CallStaticVoidMethod(clazz, staticMethodId);
LOGI("Java static method called.");
// 2. --- 调用 Java 实例方法 ---
// a. 创建 Java 类的实例
// 构造方法签名: ()V
jmethodID constructorId = env->GetMethodID(clazz, "<init>", "()V");
if (constructorId == nullptr) {
LOGE("Failed to find constructor for MyJavaClass");
return;
}
jobject instance = env->NewObject(clazz, constructorId);
if (instance == nullptr) {
LOGE("Failed to create new instance of MyJavaClass");
return;
}
// b. 获取实例方法的 ID
// 方法签名: (Ljava/lang/String;)Ljava/lang/String; -> (String)String
jmethodID instanceMethodId = env->GetMethodID(clazz, "callInstanceMethod", "(Ljava/lang/String;)Ljava/lang/String;");
if (instanceMethodId == nullptr) {
LOGE("Failed to find instance method callInstanceMethod");
return;
}
// c. 准备方法参数
jstring inputString = env->NewStringUTF("Hello from C++!");
if (inputString == nullptr) {
LOGE("Failed to create new string");
return;
}
// d. 调用实例方法
LOGI("Calling Java instance method...");
jstring resultString = (jstring) env->CallObjectMethod(instance, instanceMethodId, inputString);
LOGI("Java instance method called.");
// e. 处理返回结果
const char* cResult = env->GetStringUTFChars(resultString, nullptr);
if (cResult != nullptr) {
LOGI("Result from Java: %s", cResult);
env->ReleaseStringUTFChars(resultString, cResult); // 释放字符串资源
}
// f. 清理本地引用
// 对于在 C/C++ 中创建的 JNI 对象(如 jclass, jobject, jstring),最好在函数结束时释放
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(instance);
env->DeleteLocalRef(inputString);
env->DeleteLocalRef(resultString);
}
第 5 步:配置 CMake 或 ndk-build
在 app/build.gradle 或 CMakeLists.txt 中配置你的 C/C++ 源文件。
app/build.gradle
android {
// ...
defaultConfig {
// ...
externalNativeBuild {
cmake {
cppFlags ""
}
}
ndk {
// 指定支持的 ABI 架构,减小 APK 大小
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.18.1" // 使用合适的版本
}
}
}
app/src/main/cpp/CMakeLists.txt
# 指定 CMake 最低版本
cmake_minimum_required(VERSION 3.4.1)
# 定义一个名为 jnidemo 的共享库
add_library( # 设置库的名称
jnidemo
# 设置库的类型
SHARED
# 提供源文件的相对路径
native_bridge.cpp )
# 找到 log 库
find_library( # 设置路径变量的名称
log-lib
# 指定 NDK 库中您要定位的库名称
log )
# 将库链接到您的目标库
target_link_libraries( # 指定目标库
jnidemo
# 链接日志库
${log-lib} )
第 6 步:加载库并运行
- 同步项目:在 Android Studio 中点击 "Sync Project with Gradle Files"。
- 构建项目:Build -> Make Project。
- 调用入口:在你的 Activity 或 Application 中,调用
MyJavaClass.startJNICall()。
app/src/main/java/com/example/jnidemo/MainActivity.java
package com.example.jnidemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 启动 JNI 调用流程
MyJavaClass.startJNICall();
}
}
运行结果 (Logcat):
I/JNI_DEMO: Calling Java static method...
I/JNI_DEMO: Hello from Java's static method!
I/JNI_DEMO: Java static method called.
I/JNI_DEMO: Calling Java instance method...
I/JNI_DEMO: Java's instance method received: Hello from C++!
I/JNI_DEMO: Java instance method called.
I/JNI_DEMO: Result from Java: Processed by Java: Hello from C++!
重要注意事项
- JNIEnv 和线程:
JNIEnv是线程相关的,一个线程的JNIEnv不能在另一个线程中使用,如果你想在新的线程中调用 Java 代码,必须在该线程中通过AttachCurrentThread获取一个新的JNIEnv,并在使用完毕后通过DetachCurrentThread解除绑定。 - 引用类型:
- 局部引用:通过 JNI 函数(如
FindClass,NewObject,NewStringUTF)创建的对象引用,它们只在当前线程的函数调用期间有效,函数返回后会被自动释放,为了避免内存泄漏,特别是循环或长时间运行的函数中,应该手动调用DeleteLocalRef来释放它们。 - 全局引用:当你需要跨函数、跨线程持有对一个 Java 对象的引用时,使用
NewGlobalRef将局部引用转换为全局引用,全局引用必须手动通过DeleteGlobalRef释放,否则会造成内存泄漏。
- 局部引用:通过 JNI 函数(如
- 异常处理:JNI 调用 Java 方法可能会抛出 Java 异常,C/C++ 代码不会自动捕获这些异常,在每次可能抛出异常的 JNI 调用后,应该检查
env->ExceptionCheck(),如果为true,必须先处理异常(通常是ExceptionDescribe和ExceptionClear),然后再继续执行其他 JNI 操作,否则大部分 JNI 函数都会立即返回错误。 - 性能:JNI 调用本身是有开销的,应尽量减少 Java 和 C/C++ 之间的切换,不要在循环中进行频繁的 JNI 调用,而是将数据在 C/C++ 中处理好后,一次性返回给 Java。
通过以上步骤和示例,你应该能够掌握在 Android 中使用 C/C++ 调用 Java 代码的基本方法。
