这个过程被称为 JNI (Java Native Interface),即 Java 本地接口,它定义了一套规范,允许 Java 代码和其他语言(如 C/C++)进行交互。

下面我将分步详细解释如何在 Android C 代码中调用 Java 代码。
核心概念
- JNI 环境 (
JNIEnv):这是一个指向 JNI 环境的指针,通过这个指针,你可以访问所有 JNI 函数,查找类、创建对象、调用方法、获取字段值等,在 C/C++ 中,它通常是一个函数参数传入的指针。 - Java 对象 (
jobject,jclass,jstring等):这些是 JNI 提供的数据类型,用来代表 Java 中的对象、类、字符串等,它们本质上是指向 JVM 内部对象的句柄。 - 方法 ID (
jmethodID) 和字段 ID (jfieldID):在调用 Java 方法或访问字段之前,必须先通过类和方法/字段名称来获取它们的唯一标识符 (ID),这个 ID 可以被缓存起来,重复使用,以提高性能。
完整流程示例
假设我们有以下场景:
- Java/Kotlin 代码:定义一个
MyJavaClass类,它有一个静态方法showToast和一个实例方法getInstanceName。 - C/C++ 代码:通过 JNI 调用
showToast方法,并调用getInstanceName方法。
步骤 1: 编写 Java/Kotlin 代码
创建一个 Java 类,其中包含我们想要从 C/C++ 调用的方法。
MyJavaClass.java

package com.example.jnitest;
import android.content.Context;
import android.widget.Toast;
public class MyJavaClass {
// 一个静态方法,用于显示 Toast
public static void showToast(Context context, String message) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
// 一个实例方法,返回实例名称
public String getInstanceName() {
return "This is an instance of MyJavaClass";
}
}
步骤 2: 编写 C/C++ 代码
我们编写 C 代码来调用上述 Java 方法,你会创建一个 JNI 函数,这个函数会被 Java 代码通过 System.loadLibrary() 加载并调用。
native-lib.c
#include <jni.h>
#include <string.h>
#include <android/log.h> // 用于在 logcat 中打印日志
#define LOG_TAG "JNI_TAG"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// 这是一个 JNI 函数,它将被 Java 代码调用
// 参数 env 是 JNI 环境指针,thiz 是调用这个方法的 Java 对象 (如果是静态方法,则为 NULL)
JNIEXPORT jstring JNICALL
Java_com_example_jnitest_MainActivity_callNativeMethod(JNIEnv *env, jobject thiz) {
LOGI("Native code is running.");
// --- 1. 调用静态方法 showToast ---
// 1.1. 获取 Java 类
const char* className = "com/example/jnitest/MyJavaClass";
jclass myJavaClass = (*env)->FindClass(env, className);
if (myJavaClass == NULL) {
LOGE("Failed to find class %s", className);
return NULL;
}
// 1.2. 获取静态方法 ID
// 方法签名: (Landroid/content/Context;Ljava/lang/String;)V
// (L;...) 表示对象类型,V 表示返回 void
const char* staticMethodSignature = "(Landroid/content/Context;Ljava/lang/String;)V";
jmethodID showToastMethodID = (*env)->GetStaticMethodID(env, myJavaClass, "showToast", staticMethodSignature);
if (showToastMethodID == NULL) {
LOGE("Failed to find static method showToast");
return NULL;
}
// 1.3. 准备方法参数
// a. 获取 Context 对象 (这里我们使用 Activity 对象作为 Context)
jobject context = thiz; // thiz 在这里就是调用 callNativeMethod 的 Activity 实例
// b. 创建 Java 字符串 "Hello from C!"
jstring jMessage = (*env)->NewStringUTF(env, "Hello from C!");
// 1.4. 调用静态方法
(*env)->CallStaticVoidMethod(env, myJavaClass, showToastMethodID, context, jMessage);
// 1.5. 释放局部引用 (非常重要!)
(*env)->DeleteLocalRef(env, myJavaClass);
(*env)->DeleteLocalRef(env, jMessage);
// --- 2. 调用实例方法 getInstanceName ---
// 2.1. 获取 Java 类 (可以复用上面找到的 myJavaClass,但为了清晰,这里重新获取一次)
jclass myJavaClassInstance = (*env)->FindClass(env, className);
if (myJavaClassInstance == NULL) {
LOGE("Failed to find class %s for instance method", className);
return NULL;
}
// 2.2. 获取构造函数 ID 并创建实例
// 构造函数签名: ()V
jmethodID constructorID = (*env)->GetMethodID(env, myJavaClassInstance, "<init>", "()V");
if (constructorID == NULL) {
LOGE("Failed to find constructor");
return NULL;
}
jobject myJavaObject = (*env)->NewObject(env, myJavaClassInstance, constructorID);
// 2.3. 获取实例方法 ID
// 方法签名: ()Ljava/lang/String;
const char* instanceMethodSignature = "()Ljava/lang/String;";
jmethodID getInstanceNameMethodID = (*env)->GetMethodID(env, myJavaClassInstance, "getInstanceName", instanceMethodSignature);
if (getInstanceNameMethodID == NULL) {
LOGE("Failed to find instance method getInstanceName");
return NULL;
}
// 2.4. 调用实例方法
jstring resultString = (jstring)(*env)->CallObjectMethod(env, myJavaObject, getInstanceNameMethodID);
// 2.5. 将 jstring 转换为 C 字符串以便返回
const char* resultChars = (*env)->GetStringUTFChars(env, resultString, NULL);
if (resultChars == NULL) {
LOGE("Failed to get string chars");
return NULL;
}
// 创建一个新的 jstring 作为返回值
jstring finalResult = (*env)->NewStringUTF(env, resultChars);
// 2.6. 释放局部引用和全局资源
(*env)->ReleaseStringUTFChars(env, resultString, resultChars);
(*env)->DeleteLocalRef(env, myJavaClassInstance);
(*env)->DeleteLocalRef(env, myJavaObject);
(*env)->DeleteLocalRef(env, resultString);
return finalResult;
}
步骤 3: 在 Java/Kotlin 中声明本地方法
在你的 Activity 或其他类中,声明一个 native 方法,其签名必须与 C 函数的 JNIEXPORT JNICALL Java_... 部分完全匹配。
MainActivity.kt
package com.example.jnitest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
class MainActivity : AppCompatActivity() {
// 加载包含 native 代码的库
companion object {
init {
System.loadLibrary("jnitest") // 对应 CMakeLists.txt 中的 libjnitest
}
}
// 声明一个 native 方法
// external 关键字表示这个方法的实现在别处 (C/C++)
external fun callNativeMethod(): String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val tv: TextView = findViewById(R.id.sample_text)
// 调用 native 方法
val resultFromNative = callNativeMethod()
tv.text = "Result from C: $resultFromNative"
}
}
步骤 4: 配置 CMake 和 build.gradle
确保你的 CMake 配置正确。
app/CMakeLists.txt
cmake_minimum_required(VERSION 3.18.1)
project("jnitest")
add_library(jnitest SHARED
native-lib.c) # 你的 C/C++ 源文件
# 找到并链接 Android 的日志库
find_library(log-lib log)
# 链接日志库到你的 native 库
target_link_libraries(jnitest
${log-lib})
app/build.gradle (或 build.gradle.kts)
