Android JNI:Java继承C++库抽象类并实现回调的问题咨询
在Android中实现Java到C++的回调(JNI实战指南)
我来一步步帮你搞定这个JNI回调的问题,其实核心就是搭建Java和C++之间的桥接层,咱们分阶段拆解,每个环节都给你具体代码示例:
第一步:Java端先搞定回调接口和实现类
首先,Java没法直接继承C的抽象类,所以咱们先在Java里定义一个和C CallbackClass 对应的接口,然后写具体的实现类:
定义Java回调接口
这个接口要和C抽象类的方法一一对应,比如C有update方法,Java也定义同名同参数的方法:
// 对应C++的CallbackClass抽象类的Java接口 public interface CallbackInterface { // 参数类型可以根据你的C++方法调整,这里用String做示例 void update(String callbackData); }
写具体的回调实现类
接下来实现这个接口,把你要在回调里做的逻辑写进去,比如打印日志或者更新UI:
public class MyCallback implements CallbackInterface { @Override public void update(String callbackData) { // 这里写回调触发后的逻辑,比如打印数据 System.out.println("收到C++的回调数据:" + callbackData); // 如果要更新Android UI,记得切到主线程! // 比如在Activity里可以这么写: // runOnUiThread(() -> { // yourTextView.setText(callbackData); // }); } }
第二步:JNI层做Java和C++的桥接
这部分是核心,咱们需要写一个JNI包装类,它继承C的CallbackClass,同时持有Java回调对象的引用,负责把C的调用转发给Java。
先确认C++端的抽象类
假设你的C++库原本的抽象类是这样的:
#include <string> // C++端的抽象回调类 class CallbackClass { public: virtual ~CallbackClass() = default; // 抽象方法,等待子类实现 virtual void update(const std::string& data) = 0; };
编写JNI包装类
这个类是关键的桥接器,它要做的事情:保存Java对象的全局引用(避免局部引用被回收)、缓存Java方法的ID、重写C++的update方法来调用Java的方法:
#include <jni.h> #include "CallbackClass.h" // JNI回调包装类,继承C++的CallbackClass class JNICallbackBridge : public CallbackClass { private: JNIEnv* mEnv; jobject mJavaCallback; // Java回调对象的全局引用 jmethodID mUpdateMethodId; // 缓存的update方法ID public: // 构造函数:初始化全局引用和方法ID JNICallbackBridge(JNIEnv* env, jobject javaCallback) { mEnv = env; // 创建全局引用,因为JNI局部引用在方法返回后会被自动释放 mJavaCallback = env->NewGlobalRef(javaCallback); // 获取Java回调接口的Class对象 jclass callbackClazz = env->GetObjectClass(javaCallback); // 查找update方法的ID,方法签名必须和Java方法完全匹配 // 这里的签名"(Ljava/lang/String;)V"表示:参数是String,返回值是void mUpdateMethodId = env->GetMethodID(callbackClazz, "update", "(Ljava/lang/String;)V"); // 释放局部引用的Class对象,避免内存泄漏 env->DeleteLocalRef(callbackClazz); } // 析构函数:释放全局引用,一定要做! ~JNICallbackBridge() { mEnv->DeleteGlobalRef(mJavaCallback); } // 重写C++的update方法,转发调用到Java端 void update(const std::string& data) override { // 把C++的std::string转换成Java的String对象 jstring javaData = mEnv->NewStringUTF(data.c_str()); // 调用Java对象的update方法 mEnv->CallVoidMethod(mJavaCallback, mUpdateMethodId, javaData); // 释放局部引用的String对象 mEnv->DeleteLocalRef(javaData); // 可选:检查JNI调用是否抛出异常,避免崩溃 if (mEnv->ExceptionCheck()) { mEnv->ExceptionClear(); // 这里可以加异常处理逻辑 } } };
实现JNI注册方法
接下来写一个JNI导出方法,让Java可以调用它,把Java回调对象传递过来,然后创建桥接类实例注册到C++库:
// 假设你的C++库有一个注册回调的函数,比如: void RegisterForSubscription(CallbackClass* callback); // JNI导出方法,注意方法签名要对应Java中的Native方法 // 格式:Java_包名_类名_方法名,包名用下划线代替点 extern "C" JNIEXPORT void JNICALL Java_com_your_app_package_NativeHelper_registerCallback( JNIEnv* env, jobject /* this */, jobject javaCallback) { // 创建JNI桥接类实例 JNICallbackBridge* callbackBridge = new JNICallbackBridge(env, javaCallback); // 注册到C++库中 RegisterForSubscription(callbackBridge); }
第三步:Java端调用JNI注册回调
在Java中声明Native方法,加载JNI库,然后创建回调实例并注册:
public class NativeHelper { // 加载JNI库,库名是你编译出来的so库前缀(比如libmy-native.so,这里写my-native) static { System.loadLibrary("my-native"); } // 声明Native注册方法,参数是咱们定义的CallbackInterface public native void registerCallback(CallbackInterface callback); // 使用示例:在合适的地方调用这个方法注册回调 public void setupCallback() { MyCallback myCallback = new MyCallback(); registerCallback(myCallback); } }
第四步:C++端触发回调
当C++库需要通知Java时,直接调用CallbackClass的update方法就行,因为咱们的桥接类已经重写了这个方法,会自动转发到Java端:
// 假设C++库中有某个业务逻辑需要触发回调 void triggerCallback() { // 这里假设你已经保存了注册进来的CallbackClass指针 CallbackClass* registeredCallback = getRegisteredCallback(); if (registeredCallback != nullptr) { // 调用update方法,会自动触发Java端的回调 registeredCallback->update("这是来自C++的回调消息!"); } }
几个必须注意的坑
- 全局引用一定要释放:如果忘了在析构函数里调用
DeleteGlobalRef,会导致Java对象无法被GC回收,造成内存泄漏。 - 方法签名不能错:JNI查找方法时签名必须完全匹配,你可以用
javap -s 你的类名命令生成正确的签名,比如对CallbackInterface执行这个命令,就能看到update方法的签名。 - 线程问题:如果C++是在子线程调用回调,那Java的
update方法会运行在同一个子线程,Android更新UI必须在主线程,所以记得用runOnUiThread或者Handler切换线程。 - 异常处理:JNI调用Java方法可能会抛出异常,一定要检查并清除,否则会导致程序崩溃。
内容的提问来源于stack exchange,提问作者Chris R
相关产品推荐
相关产品推荐

