java 和 jni 相互调用

围巾🧣 2024年06月26日 197次浏览

项目接入 C ++

1. 创建文件夹

main 下面创建 cpp 文件夹 src/main/cpp

在该文件夹中创建一个简单的 C++ 文件,例如 native-lib.cpp

// src/main/cpp/native-lib.cpp
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

2. 创建 CMakeLists.txt

src/main/cpp 文件夹中创建一个 CMakeLists.txt 文件:

# 指定 CMake 的最低版本要求。
cmake_minimum_required(VERSION 3.4.1)

# 添加一个库,设置库的名称为 native-lib,并指定它是一个共享库(即动态链接库),
# 然后提供一个相对路径到源文件 native-lib.cpp。
add_library(
        native-lib          # 库的名称
        SHARED              # 库类型:共享库(动态库)
        native-lib.cpp      # 源文件
)

# 查找 log 库,并将其路径存储在 log-lib 变量中。
find_library(
        log-lib             # 路径变量的名称
        log                 # 要查找的 NDK 库名称
)

# 指定目标库,并链接到 log 库。
target_link_libraries(
        native-lib          # 目标库,即我们要生成的 native-lib 库
        ${log-lib}          # 要链接的库,即在上一步中找到的 log 库
)

3. 修改 build.gradle

在你的 app 级别 build.gradle 文件中添加 externalNativeBuild 配置:

android {
    ...
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
        }
    }
    ndkVersion '20.0.5594570'
}

dependencies {
    ...
}

4. 声明本地方法

在你的 MainActivity 中声明本地方法:

// MainActivity.java
package com.example.myapplication;

import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    public native String stringFromJNI();
}

jni 回调 java 层的方法

在 JNI(Java Native Interface)中,从本地代码回调 Java 方法涉及几个步骤,包括获取 Java 类和方法的引用,并使用 JNI 提供的 API 调用该方法。以下是一个详细的步骤说明以及示例代码,展示如何从 JNI 回调 Java 层的 onError(int errorMsg) 方法。

1. 定义 Java 方法

首先,确保在 Java 类中定义了 onError(int errorMsg) 方法。例如:

public class MyClass {
    public void onError(int errorMsg) {
        // 错误处理代码
        System.out.println("Error: " + errorMsg);
    }
}

2. 声明 Native 方法

在 Java 类中声明一个 native 方法,用于从本地代码触发回调。例如:

public class MyClass {
    public void onError(int errorMsg) {
        // 错误处理代码
        System.out.println("Error: " + errorMsg);
    }

    // 声明 native 方法
    public native void nativeMethod();

    // 加载本地库
    static {
        System.loadLibrary("native-lib");
    }
}

3. 实现 Native 方法

在本地代码中实现 nativeMethod,并在该方法中调用 onError 方法。以下是一个完整的示例:

实现文件 ( native-lib.cpp)

编写本地方法的实现文件,例如 MyClass.c

#include <jni.h>
#include <stdio.h>

// 实现 nativeMethod
extern "C"
JNIEXPORT void JNICALL Java_top_xlxs_studydemo_ndk_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
    // 获取 MyClass 类
    jclass cls = env->GetObjectClass(obj);
    if (cls == NULL) {
        return; // 类未找到
    }

    // 获取 onError 方法的 ID
    jmethodID mid = env->GetMethodID(cls, "onError", "(I)V");
    if (mid == NULL) {
        return; // 方法未找到
    }

    // 调用 onError 方法,传递错误码 10086
    env->CallVoidMethod(obj, mid, 10086);
}

参数对应关系

在 JNI 中,Java 方法的签名由方法的参数类型和返回类型组成。对于 onError(int errorMsg) 方法,其签名是 (I)V,其中:

  • I 表示 int 类型参数。
  • V 表示 void 返回类型。

在本地代码中,通过 GetMethodID 函数获取方法 ID,需要提供类引用、方法名和方法签名。然后,使用 CallVoidMethod 函数调用该方法,并传递相应的参数。

如果方法为 onError(int errorCode, String errorMsg) ,则对应如下:

#include <jni.h>
#include <stdio.h>

// 实现 nativeMethod
extern "C"
JNIEXPORT void JNICALL Java_top_xlxs_studydemo_ndk_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
    // 获取 MyClass 类
    jclass cls = env->GetObjectClass(obj);
    if (cls == NULL) {
        return; // 类未找到
    }

    // 获取 onError 方法的 ID
    jmethodID mid = env->GetMethodID(cls, "onError", "(ILjava/lang/String;)V");
    if (mid == NULL) {
        return; // 方法未找到
    }

    // 创建一个 Java 字符串
    jstring jErrorMsg = env->NewStringUTF("一些错误信息");

    // 调用 onError 方法,传递错误码和错误信息
    env->CallVoidMethod(obj, mid, 10086, jErrorMsg);

    // 删除局部引用
    env->DeleteLocalRef(jErrorMsg);
}

对于 onError(int errorCode, String errorMsg) 方法,其签名是 (ILjava/lang/String;)V,其中:

  • I 表示 int 类型参数。
  • Ljava/lang/String; 表示 String 类型参数。
  • V 表示 void 返回类型。

JNI 方法签名例子

在 JNI 中,每个参数类型都有一个特定的表示符号。多个参数类型直接写在一起,形成一个连续的字符串。方法签名由参数类型和返回类型组成,中间没有任何分隔符。以下是一些常见的 JNI 类型表示:

  • Z - boolean
  • B - byte
  • C - char
  • S - short
  • I - int
  • J - long
  • F - float
  • D - double
  • L<classname>; - 对象类型,其中 classname 是类的全限定名,使用斜杠 / 作为分隔符
  • [type - 数组类型,其中 type 是数组的元素类型
  • V - void 返回类型

方法签名的格式为:

(参数类型...)返回类型

具体到 onError(int errorCode, String errorMsg) 方法,它有两个参数:一个 int 类型和一个 String 类型,返回类型是 void。因此,其 JNI 签名为:

(ILjava/lang/String;)V
  • () 之间的是参数列表:
    • I 表示 int 类型。
    • Ljava/lang/String; 表示 String 类型,其中 Ljava/lang/String; 表示一个 Java 对象类型,这里的 java/lang/StringString 类的全限定名,L 开头,java/lang/String 是类名,用 / 分隔包名,最后一个 ; 表示对象类型的结束。
  • V 表示返回类型是 void

举例说明

假设您有一个方法 foo,它的签名为:

public String foo(int a, boolean b, double[] arr, String msg)

对应的 JNI 签名为:

(IZ[DLjava/lang/String;)Ljava/lang/String;

表示方法参数,其中:

  • I 表示 int 参数。
  • Z 表示 boolean 参数。
  • [D 表示 double 数组参数。
  • Ljava/lang/String; 表示 String 参数。

Ljava/lang/String; 表示返回类型是 String

#include <jni.h>
#include <stdio.h>
#include <string>

// 实现 nativeMethod
extern "C"
JNIEXPORT jstring JNICALL Java_top_xlxs_studydemo_ndk_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
    // 获取 MyClass 类
    jclass cls = env->GetObjectClass(obj);
    if (cls == NULL) {
        return env->NewStringUTF("Class not found"); // 类未找到
    }

    // 获取foo方法的ID
    jmethodID fooMethod = env->GetMethodID(cls, "foo", "(IZ[DLjava/lang/String;)Ljava/lang/String;");
    if (fooMethod == NULL) {
        return env->NewStringUTF("Method ID not found");
    }

    // 准备参数
    jint a = 10086;
    jboolean b = JNI_TRUE;
    jdoubleArray arr = env->NewDoubleArray(3);
    jdouble elements[] = {1.0, 2.0, 3.0};
    env->SetDoubleArrayRegion(arr, 0, 3, elements);
    jstring msg = env->NewStringUTF("Hello from JNI");

    // 调用Java方法
    auto result = (jstring) env->CallObjectMethod(obj, fooMethod, a, b, arr, msg);

    // 获取结果字符串
    const char *resultCStr = env->GetStringUTFChars(result, NULL);

    // 打印结果(或其他处理)
    std::string resultStr = std::string(resultCStr);

    // 释放资源
    env->ReleaseStringUTFChars(result, resultCStr);
    env->DeleteLocalRef(arr);
    env->DeleteLocalRef(msg);

    // 返回结果
    return env->NewStringUTF(resultStr.c_str());
}

扩展

在 JNI 中,除了 CallVoidMethod 外,还有许多其他方法可以调用 Java 对象的方法。根据方法的返回类型,使用不同的 Call<Type>Method 方法。这些方法包括:

根据返回类型调用方法

  • CallBooleanMethod:调用返回 boolean 的方法。
  • CallByteMethod:调用返回 byte 的方法。
  • CallCharMethod:调用返回 char 的方法。
  • CallShortMethod:调用返回 short 的方法。
  • CallIntMethod:调用返回 int 的方法。
  • CallLongMethod:调用返回 long 的方法。
  • CallFloatMethod:调用返回 float 的方法。
  • CallDoubleMethod:调用返回 double 的方法。
  • CallObjectMethod:调用返回 Object 的方法。

播放器场景的例子

举例从 Java 调用 JNI 函数,以及从 JNI 调用 Java 方法。下面通过简化的 JinPlayer 类和 JNICallbackHelper 类来展示这一过程

Java 端代码

JinPlayer 类中,定义了一些 native 方法和回调方法,这些方法会在 JNI 中被调用:

public class JinPlayer implements SurfaceHolder.Callback {

    static {
        System.loadLibrary("native-lib");
    }

    private OnPreparedListener onPreparedListener;
    private OnErrorListener onErrorListener;
    private OnProgressListener onProgressListener;
    private SurfaceHolder surfaceHolder;

    public JinPlayer() {
    }

    private String dataSource;

    public void setDataSource(String dataSource) {
        this.dataSource = dataSource;
    }

    public void prepare() {
        prepareNative(dataSource);
    }

    public void start() {
        startNative();
    }

    public void stop() {
        stopNative();
    }

    public void release() {
        releaseNative();
    }

    public void onPrepared() {
        if (onPreparedListener != null) {
            onPreparedListener.onPrepared();
        }
    }

    public void onError(int errorCode) {
        if (onErrorListener != null) {
            onErrorListener.onError(errorCode);
        }
    }

    public void onProgress(int progress) {
        if (onProgressListener != null) {
            onProgressListener.onProgress(progress);
        }
    }

    private native void prepareNative(String dataSource);
    private native void startNative();
    private native void stopNative();
    private native void releaseNative();
    private native void setSurfaceNative(Surface surface);
    private native int getDurationNative();
    private native void seekNative(int playValue);
}

JNI 端回调代码

在 JNI 端,我们实现 JNICallbackHelper 类,用于从 JNI 回调 Java 方法:

#include "JNICallbackHelper.h"

JNICallbackHelper::JNICallbackHelper(JavaVM *vm, JNIEnv *env, jobject job) {
    this->vm = vm;
    this->env = env;
    this->job = env->NewGlobalRef(job);

    jclass clazz = env->GetObjectClass(job);
    jmd_prepared = env->GetMethodID(clazz, "onPrepared", "()V");
    jmd_onError = env->GetMethodID(clazz, "onError", "(I)V");
    jmd_onProgress = env->GetMethodID(clazz, "onProgress", "(I)V");
}

JNICallbackHelper::~JNICallbackHelper() {
    vm = nullptr;
    env->DeleteGlobalRef(job);
    job = nullptr;
    env = nullptr;
}

void JNICallbackHelper::onPrepared(int thread_mode) {
    if (thread_mode == THREAD_MAIN) {
        env->CallVoidMethod(job, jmd_prepared);
    } else {
        JNIEnv *env_child;
        vm->AttachCurrentThread(&env_child, nullptr);
        env_child->CallVoidMethod(job, jmd_prepared);
        vm->DetachCurrentThread();
    }
}

void JNICallbackHelper::onError(int thread_mode, int error_code) {
    if (thread_mode == THREAD_MAIN) {
        env->CallVoidMethod(job, jmd_onError, error_code);
    } else {
        JNIEnv *env_child;
        vm->AttachCurrentThread(&env_child, nullptr);
        env_child->CallVoidMethod(job, jmd_onError, error_code);
        vm->DetachCurrentThread();
    }
}

void JNICallbackHelper::onProgress(int thread_mode, int audio_time) {
    if (thread_mode == THREAD_MAIN) {
        env->CallVoidMethod(job, jmd_onProgress, audio_time);
    } else {
        JNIEnv *env_child;
        vm->AttachCurrentThread(&env_child, nullptr);
        env_child->CallVoidMethod(job, jmd_onProgress, audio_time);
        vm->DetachCurrentThread();
    }
}

JNI 方法实现

在 JNI 中实现 native 方法,例如 prepareNative

#include <jni.h>
#include "JNICallbackHelper.h"

// 假设 JNICallbackHelper 对象已经初始化
JNICallbackHelper *callbackHelper = nullptr;

extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_prepareNative(JNIEnv *env, jobject thiz, jstring data_source) {
    // 初始化 JNICallbackHelper
    if (callbackHelper == nullptr) {
        JavaVM *vm;
        env->GetJavaVM(&vm);
        callbackHelper = new JNICallbackHelper(vm, env, thiz);
    }

    // 这里添加准备视频播放的逻辑
    // 当准备完成时调用 callbackHelper->onPrepared(...)
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_startNative(JNIEnv *env, jobject thiz) {
    // 开始播放逻辑
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_stopNative(JNIEnv *env, jobject thiz) {
    // 停止播放逻辑
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_releaseNative(JNIEnv *env, jobject thiz) {
    // 释放资源逻辑
    if (callbackHelper != nullptr) {
        delete callbackHelper;
        callbackHelper = nullptr;
    }
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_setSurfaceNative(JNIEnv *env, jobject thiz, jobject surface) {
    // 设置 Surface 逻辑
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_JinPlayer_getDurationNative(JNIEnv *env, jobject thiz) {
    // 获取视频时长逻辑
    return 0;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_seekNative(JNIEnv *env, jobject thiz, jint play_value) {
    // 视频跳转逻辑
}