杰瑞科技汇

Android C如何调用Java方法?

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

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

下面我将详细解释整个流程,从环境搭建到具体的调用方法,并提供一个完整的示例。


核心概念与流程

JNI 的本质:C/C++ 代码本身不能直接“理解”或“执行”Java 代码,JNI 提供了一组 API,让 C/C++ 代码能够:

  1. 获取 JVM (Java Virtual Machine) 的实例:这是所有 Java 操作的入口。
  2. 通过 JVM 找到目标 Java 类:通常是通过类的全限定名(如 java/lang/String)。
  3. 获取类的 ID (Class ID):这是一个指向类定义的句柄。
  4. 获取方法或字段的 ID:通过方法名、签名和类 ID,找到具体的 Java 方法或字段。
  5. 调用方法或访问字段:使用上一步获取的 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]

详细步骤与代码示例

我们将创建一个简单的示例,

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

Android C如何调用Java方法?-图3
(图片来源网络,侵删)
package com.example.jnidemo;
public class NativeBridge {
    // 声明一个 native 方法,这个方法在 C 中实现
    // 这个方法将作为我们 C 代码的入口点
    public native void callJavaMethodsFromC();
}

第 3 步:生成 JNI 头文件

这是最关键的一步,它定义了 C/C++ 函数的签名,以便与 Java 的 native 方法对应。

  1. 编译 Java 代码:确保你的 Java 代码已经编译成功,Android Studio 通常会自动处理。

  2. 使用 javacjavah (或 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
    #endif
    • JNIEXPORT, 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.gradleCMakeLists.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 步:加载库并运行

  1. 同步项目:在 Android Studio 中点击 "Sync Project with Gradle Files"。
  2. 构建项目:Build -> Make Project。
  3. 调用入口:在你的 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++!

重要注意事项

  1. JNIEnv 和线程JNIEnv线程相关的,一个线程的 JNIEnv 不能在另一个线程中使用,如果你想在新的线程中调用 Java 代码,必须在该线程中通过 AttachCurrentThread 获取一个新的 JNIEnv,并在使用完毕后通过 DetachCurrentThread 解除绑定。
  2. 引用类型
    • 局部引用:通过 JNI 函数(如 FindClass, NewObject, NewStringUTF)创建的对象引用,它们只在当前线程的函数调用期间有效,函数返回后会被自动释放,为了避免内存泄漏,特别是循环或长时间运行的函数中,应该手动调用 DeleteLocalRef 来释放它们。
    • 全局引用:当你需要跨函数、跨线程持有对一个 Java 对象的引用时,使用 NewGlobalRef 将局部引用转换为全局引用,全局引用必须手动通过 DeleteGlobalRef 释放,否则会造成内存泄漏。
  3. 异常处理:JNI 调用 Java 方法可能会抛出 Java 异常,C/C++ 代码不会自动捕获这些异常,在每次可能抛出异常的 JNI 调用后,应该检查 env->ExceptionCheck(),如果为 true,必须先处理异常(通常是 ExceptionDescribeExceptionClear),然后再继续执行其他 JNI 操作,否则大部分 JNI 函数都会立即返回错误。
  4. 性能:JNI 调用本身是有开销的,应尽量减少 Java 和 C/C++ 之间的切换,不要在循环中进行频繁的 JNI 调用,而是将数据在 C/C++ 中处理好后,一次性返回给 Java。

通过以上步骤和示例,你应该能够掌握在 Android 中使用 C/C++ 调用 Java 代码的基本方法。

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