JNI全局回调java方法

简介: JNI全局回调java方法

一、实现思路:


 

    1、jni里面调用java方法的大致步骤是:根据jobject获取jclass(静态方法就不用这一步了)--> 获取jmethodid --> 调用方法。
    2、jni里面调用java方法的环境分为2种。
    第一种:在env所在线程调用java方法,这种情况不需要做特殊处理,直接按照步骤执行即可。
    第二种:在pthread子线程调用java方法,这种情况下就需要做处理了。在jni中,子线程中是不能直接调用JNIEnv对象的,也不能直接调用env线程中的jobject对象,因为:jni中,JNIEnv是和线程相关的,每一个native方法所在线程就有一个当前线程相关的JNIEnv对象,而pthread线程中是不能调用native方法所在线程的JENnv对象的,解决办法是:利用JavaVM虚拟机,JavaVM是和进程相关的,一个进程里面的JavaVM都是同一个,所以在pthread线程中就可以通过JavaVM来获取(AttachCurrentThread)当前线程的JNIEnv指针,然后就可以使用JNIEnv指针操作数据了;还有在pthread线程中调用jobject对象时,首先需要把native线程里面的jobject创建全局引用(env->NewGlobalRef(jobj)),其返还的jobject对象就可以在程序中使用了。
    3、在JNI_OnLoad中获取我们需要的JavaVM指针。


二、代码实现:


2.1、创建文件WlListener.cpp来统一管理回调方法,并在构造方法中传入所需参数。


头文件WlListener.h

//
// Created by ywl5320
//
#pragma once
#ifndef JNITHREAD_WLLISTENER_H
#define JNITHREAD_WLLISTENER_H
#include <jni.h>
class WlListener {
public:
JavaVM* jvm;//java虚拟机
_JNIEnv *jenv;//native线程env对象
jobject jobj;//全局对象
jmethodID jmid;//java 方法id,可以根据实际情况创建多个。
public:
WlListener(JavaVM* vm, _JNIEnv *jenv, jobject obj);
~WlListener();
void onError(int type, int code, const char *msg);
};
#endif //JNITHREAD_WLLISTENER_H


cpp文件WlListener.cpp

//
// Created by ywl5320
//
#include "WlListener.h"
#include "WlAndroidLog.h"
WlListener::WlListener(JavaVM *vm, _JNIEnv *env, jobject obj) {
    jvm = vm;
    jenv = env;
    jobj = obj;
    jclass clz = env->GetObjectClass(jobj);
    if(!clz)
    {
        LOGE("get jclass wrong");
        return;
    }
    jmid = env->GetMethodID(clz, "onError", "(ILjava/lang/String;)V");
    if(!jmid)
    {
        LOGE("get jmethodID wrong");
        return;
    }
}
/**
 *
 * @param type  0:env线程 1:子线程
 * @param code
 * @param msg
 */
void WlListener::onError(int type, int code, const char *msg) {
    if(type == 0)
    {
        jstring jmsg = jenv->NewStringUTF(msg);
        jenv->CallVoidMethod(jobj, jmid, code, jmsg);
        jenv->DeleteLocalRef(jmsg);
    }
    else if(type == 1)
    {
        JNIEnv *env;
        jvm->AttachCurrentThread(&env, 0);
        jstring jmsg = env->NewStringUTF(msg);
        env->CallVoidMethod(jobj, jmid, code, jmsg);
        env->DeleteLocalRef(jmsg);
        jvm->DetachCurrentThread();
    }
}


其中:jvm参数是为了获取子线程中的JNIEnv;jenv参数是native线程中的,在native线程中使用;jobj是全局对象;jmid是要调用的java层的方法id,还可以有其他方法。

2.2、通过JNI_OnLoad获取JavaVM:
JavaVM* jvm;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm,void* reserved){
    JNIEnv *env;
    jvm = vm;
    if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK){
        return -1;
    }
    return JNI_VERSION_1_6;
}


JNI_OnLoad方法是在加载完.so库时就会自动调用的,所以在这里获取JavaVM是最佳的时机。

 

2.3、主线程和子线程调用回调方法
pthread_t callbackThread;
void *callBackT(void *data)
{
    //获取WlListener指针
    WlListener *wlListener = (WlListener *) data;
    //在子线程中调用回调方法
    wlListener->onError(1, 200, "Child thread running success!");
    pthread_exit(&callbackThread);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_ywl5320_jnithread_JniThread_callbackThread(JNIEnv *env, jobject jobj) {
    WlListener *wlListener = new WlListener(jvm, env, env->NewGlobalRef(jobj));
    //在主线程中调用java方法
    wlListener->onError(0, 100, "JNIENV thread running success!");
    //开启子线程,并把WlListener指针传递到子线程中
    pthread_create(&callbackThread, NULL, callBackT, wlListener);
}
    2.4、java方法
//3、回调线程
  public native void callbackThread();
private OnErrorListener onErrorListener;
public void setOnErrorListener(OnErrorListener onErrorListener) {
    this.onErrorListener = onErrorListener;
}
//Jni调用此方法,把结果返回到java层
public void onError(int code, String msg)
{
    if(onErrorListener != null)
    {
        onErrorListener.onError(code, msg);
    }
}
public interface OnErrorListener
{
    void onError(int code, String msg);
}


这样就完成了在C/C++中不同线程回调java方法了。

原文:https://blog.csdn.net/ywl5320/article/details/78739211

相关文章
|
3天前
|
存储 安全 算法
Java容器及其常用方法汇总
Java Collections框架提供了丰富的接口和实现类,用于管理和操作集合数据。
Java容器及其常用方法汇总
|
5天前
|
存储 缓存 Java
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
20 3
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
|
3天前
|
Java API
java.time常用方法汇总
`java.time` API 是从 Java 8 开始引入的时间日期处理库,旨在替代老旧的 `java.util.Date` 和 `Calendar`。它提供了更简洁、强大和灵活的方式处理日期、时间、时区及时间间隔,支持全球化和时间计算需求。API 包含获取当前时间、创建指定时间、解析和格式化字符串、进行加减运算、比较时间、获取年月日时分秒、计算时间间隔、时区转换以及判断闰年等功能。示例代码展示了如何使用这些功能,极大简化了开发中的时间处理任务。
|
26天前
|
Java
Java快速入门之类、对象、方法
本文简要介绍了Java快速入门中的类、对象和方法。首先,解释了类和对象的概念,类是对象的抽象,对象是类的具体实例。接着,阐述了类的定义和组成,包括属性和行为,并展示了如何创建和使用对象。然后,讨论了成员变量与局部变量的区别,强调了封装的重要性,通过`private`关键字隐藏数据并提供`get/set`方法访问。最后,介绍了构造方法的定义和重载,以及标准类的制作规范,帮助初学者理解如何构建完整的Java类。
|
22天前
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
55 9
|
27天前
|
安全 Java 程序员
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
60 12
|
28天前
|
算法 Java API
Java 方法注释:规范、实用和高质量的写法
本文深入探讨了如何编写高质量的 Java 方法注释
50 11
|
2天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
35 14