调用步骤:
// example.cpp
#include <jni.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_MyClass_myFunction(JNIEnv* env, jobject /* this */) {
return env->NewStringUTF("Hello from C++");
}
class MyClass {
external fun myFunction(): String
companion object {
init {
System.loadLibrary("example") // example是库的名字
}
}
}
val myClass = MyClass()
println(myClass.myFunction()) // 输出 "Hello from C++"
在 Flutter 插件中引用已有的 C++ 源码需要以下步骤:
cmake_minimum_required(VERSION 3.4.1)
add_library( # Specifies the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
android {
// ...
defaultConfig {
// ...
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
System.loadLibrary
来加载库,并使用 external
关键字来声明 native 方法。class MyPlugin: FlutterPlugin, MethodCallHandler {
// ...
external fun myNativeMethod(): String
init {
System.loadLibrary("native-lib")
}
// ...
}
#include <jni.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_MyPlugin_myNativeMethod(JNIEnv* env, jobject /* this */) {
// 你的代码...
}
ndk-build
或者 CMake 来编译 C++ 代码,并将生成的库放到 Android 项目的 jniLibs
目录下。Java_包名_类名_方法名
。在这个例子中,Java_com_example_MyClass_myFunction
对应于 com.example.MyClass
类的 myFunction
方法。在 Kotlin 中,可以使用 JNI (Java Native Interface) 来设置回调到 C++ 代码。
interface Callback {
fun onEvent(event: String)
}
class MyClass {
private var callback: Callback? = null
fun setCallback(callback: Callback) {
this.callback = callback
}
external fun triggerEvent()
private fun onEvent(event: String) {
callback?.onEvent(event)
}
companion object {
init {
System.loadLibrary("example")
}
}
}
triggerEvent
函数,从参数中获取 onEvent
方法并调用。#include <jni.h>
extern "C" JNIEXPORT void JNICALL
Java_com_example_MyClass_triggerEvent(JNIEnv* env, jobject instance) {
jclass cls = env->GetObjectClass(instance);
jmethodID mid = env->GetMethodID(cls, "onEvent", "(Ljava/lang/String;)V");
if (mid == nullptr) return; // method not found
jstring message = env->NewStringUTF("Hello from C++");
env->CallVoidMethod(instance, mid, message);
}
在这个例子中,triggerEvent
函数在 C++ 代码中被调用,然后它调用 Kotlin 中的 onEvent
方法,该方法然后调用 Callback
接口的 onEvent
方法。
在 JNI (Java Native Interface) 中,“(Ljava/lang/String;)V” 是一个方法签名,用于描述方法的参数类型和返回值类型
这个签名可以被分解为以下部分:
括号 “(” 和 “)”:括号内的内容描述了方法的参数类型。在这个例子中,“Ljava/lang/String;” 表示方法有一个参数,类型为 java.lang.String
。
“V”:这是方法的返回值类型。在 JNI 中,“V” 表示 void,也就是说这个方法没有返回值。
所以,“(Ljava/lang/String;)V” 这个签名表示的是一个接受一个 java.lang.String
参数并且没有返回值的方法。
其他一些常见的 JNI 类型签名包括:
你可以在 JNI 文档中找到更多关于类型签名的信息。
在 JNI 中,JNIEnv*
和 jobject
通常不能直接保存起来用于异步回调。
这是因为:
JNIEnv*
是线程相关的,每个线程都有一个不同的 JNIEnv*
。如果你在一个线程保存了 JNIEnv*
,然后在另一个线程使用它,可能会导致问题。jobject
是一个局部引用,它只在当前的 JNI 调用中有效,调用结束后就会被自动删除。如果你需要在异步回调中使用这些对象,你需要做一些额外的步骤:
对于 JNIEnv*
,你需要在回调的线程中通过 JavaVM*
获取一个新的 JNIEnv*
。你可以在保存 JNIEnv*
的同时保存 JavaVM*
,通过调用 JNIEnv->GetJavaVM(&jvm)
获取。
对于 jobject
,你需要创建一个全局引用,这样它就可以跨越多个 JNI 调用。你可以通过调用 JNIEnv->NewGlobalRef(jobject)
来创建一个全局引用。记住在你不再需要这个全局引用时,需要调用 JNIEnv->DeleteGlobalRef(jobject)
来删除它,防止内存泄漏。
以下是一个简单的例子:
JavaVM* jvm;
jobject globalObj;
JNIEXPORT void JNICALL Java_MyClass_init(JNIEnv* env, jobject obj) {
env->GetJavaVM(&jvm);
globalObj = env->NewGlobalRef(obj);
}
void asyncCallback() {
JNIEnv* env;
jvm->AttachCurrentThread(&env, NULL);
// 有了env跟obj后,这里参考上面同步调用的例子的实现
jvm->DetachCurrentThread();
}
在这个例子中,Java_MyClass_init
是一个 JNI 方法,它保存了 JavaVM*
和一个全局引用。然后在 asyncCallback
中,我们获取了一个新的 JNIEnv*
,并使用了全局引用。注意我们在回调结束时调用了 DetachCurrentThread
,这是因为我们之前调用了 AttachCurrentThread
。如果你在一个已经被附加到 JVM 的线程中调用回调,你不需要调用这两个方法。