kotlin一次通过jni调用C++侧,然后C++侧多次回传数据
让C++通过JNI来callback回调kotlin侧代码
1. 定义Kotlin接口
在Kotlin中定义一个用于接收C++回调的接口:
interface DataCallback {
fun onDataReceived(data: String)
}
2. 定义C++侧的JNI方法
在C++侧,实现接收指令和回调对象的JNI方法,并存储回调对象的引用以便后续调用。
#include <jni.h>
#include <string>
#include <thread>
#include <chrono>
JavaVM* javaVM;
jobject globalCallbackRef;
// 初始化全局Java VM
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
javaVM = vm;
return JNI_VERSION_1_6;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_yourapp_MainActivity_sendCommand(JNIEnv* env, jobject obj, jstring command, jobject callback) {
// 保存全局回调对象引用
globalCallbackRef = env->NewGlobalRef(callback);
// 将jstring转换为C++字符串
const char* commandStr = env->GetStringUTFChars(command, nullptr);
std::string cmd(commandStr);
env->ReleaseStringUTFChars(command, commandStr);
// 启动一个新线程来模拟处理过程并多次回传数据
std::thread([cmd]() {
for (int i = 0; i < 5; ++i) {
// 模拟处理并生成回传数据
std::string data = "Data " + std::to_string(i);
// 回传数据给Kotlin
JNIEnv* env;
javaVM->AttachCurrentThread(&env, nullptr);
jclass callbackClass = env->GetObjectClass(globalCallbackRef);
jmethodID onDataReceivedMethod = env->GetMethodID(callbackClass, "onDataReceived", "(Ljava/lang/String;)V");
jstring dataStr = env->NewStringUTF(data.c_str());
env->CallVoidMethod(globalCallbackRef, onDataReceivedMethod, dataStr);
env->DeleteLocalRef(dataStr);
javaVM->DetachCurrentThread();
// 模拟延迟
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// 释放全局回调对象引用
JNIEnv* env;
javaVM->AttachCurrentThread(&env, nullptr);
env->DeleteGlobalRef(globalCallbackRef);
javaVM->DetachCurrentThread();
}).detach();
}
3. Kotlin中调用示例1
在Kotlin中直接调用sendCommand
并设置回调:
class MainActivity : AppCompatActivity(), DataCallback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 执行命令并设置回调
sendCommand("start", this)
}
override fun onDataReceived(data: String) {
// 处理回传数据
Log.d("MainActivity", "Received data: $data")
}
init {
System.loadLibrary("native-lib")
}
}
4. 调用示例2, 让sendCommand直接获取到callback对象,而不是继承和接口实现!!!
// 定义回调接口
interface DataCallback {
fun onDataReceived(data: String)
}
// 定义一个单例 JniManager 用于管理 JNI 调用
object JniManager {
external fun sendCommand(command: String, callback: DataCallback)
init {
System.loadLibrary("native-lib")
}
}
// MainActivity 中的实现
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 实现回调接口
val callback = object : DataCallback {
override fun onDataReceived(data: String) {
// 处理回传数据
Log.d("MainActivity", "Received data: $data")
}
}
// 执行命令并设置回调
JniManager.sendCommand("start", callback)
}
}
5. 代码解释
- 全局引用:使用
NewGlobalRef
创建全局引用,以防止回调对象在C++处理期间被垃圾回收。 - 多次回传数据:在一个新线程中,通过循环调用Kotlin的回调方法多次回传数据。
- 线程处理:使用
std::thread
来模拟数据处理并进行回调,避免阻塞主线程。
这样可以在Kotlin和C++之间高效地进行多次数据传递和回调,而无需额外的包装函数。
6. JavaVM和JNIEnv的作用
在JNI中,JavaVM和JNIEnv是两个核心接口,用于在C++和Java之间进行交互。理解这两个接口的作用对于正确实现JNI回调非常重要。
JavaVM和JNIEnv的作用
• JavaVM:这是一个全局指针,指向Java虚拟机实例。你可以通过它在不同线程中获取JNIEnv指针。
• JNIEnv:这是一个线程局部指针,提供了在当前线程中与Java对象交互的方法。每个线程都有自己的JNIEnv指针。
JNI_OnLoad函数
JNI_OnLoad函数是在JNI库加载时自动调用的函数。它用于初始化和注册本地方法,并可以保存全局的JavaVM指针,以便在其他线程中使用。通过这个函数,你可以在库加载时进行一些初始化工作。
为什么需要保存JavaVM指针?
当你需要在多个线程中与Java虚拟机交互时,你不能直接使用当前线程的JNIEnv指针,因为JNIEnv是线程局部的。相反,你可以通过全局的JavaVM指针在任何线程中获取JNIEnv指针。
7. 非常重要的说明
在JNI的C++代码中,std::thread([])新开启的线程模拟产生数据,在线程内如果无法获取到kotlin到class到时候(获取错误),则可以先在线程外的env成功获取对应class,再保存为全局引用,从而被线程内的新的env使用。
详细参考的我的另一篇博客 https://blog.csdn.net/tyfwin/article/details/140634231?spm=1001.2014.3001.5501