杰瑞科技汇

Android C如何调用Java方法?

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

Android C如何调用Java方法?-图1
(图片来源网络,侵删)

下面我将分步详细解释如何在 Android C 代码中调用 Java 代码。


核心概念

  1. JNI 环境 (JNIEnv):这是一个指向 JNI 环境的指针,通过这个指针,你可以访问所有 JNI 函数,查找类、创建对象、调用方法、获取字段值等,在 C/C++ 中,它通常是一个函数参数传入的指针。
  2. Java 对象 (jobject, jclass, jstring 等):这些是 JNI 提供的数据类型,用来代表 Java 中的对象、类、字符串等,它们本质上是指向 JVM 内部对象的句柄。
  3. 方法 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

Android C如何调用Java方法?-图2
(图片来源网络,侵删)
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)

                                    
分享:
扫描分享到社交APP
上一篇
下一篇