Java调用C++
Java调用C++的关键在于建立Java方法与C++方法的对应关系。这个关系建立在C++层。一般会在C++层中再抽象出一个JNI层,实际也是C++的代码。也就是JNI层的代码只负责建立对应关系,且在方法实现中转调实现具体逻辑的C++代码。
JNINativeMethod
结构体JNINativeMethod用来描述对应关系:
typedef struct {
const char* name; //Java方法的名字
const char* signature; //Java方法的签名信息
void* fnPtr; //JNI层中对应的方法指针
} JNINativeMethod;
可见,方法名和签名信息唯一标示一个Java方法。
Java方法签名信息
Java方法签名信息由参数列表和返回值组成,格式为:(参数签名…)返回值签名。
其中,参数签名和返回值签名,其实就是把Java类型用1个字符串描述。具体的对应关系,详见libnativehelper/include/nativehelper/jni.h
// 例如这个,即Java方法参数有3个,类型依次是Object、String、String;返回值类型为void。
(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V
注册对应关系
Java层入口方法为System.loadLibrary()。
void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
// loader.findLibrary()即从指定的目录中找到名为libraryName的文件。
// “指定的目录”相关代码在LoadedApk.getClassLoader()。
String filename = loader.findLibrary(libraryName);
if (filename == null) {
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
// doLoad()会调用到Native方法nativeLoad()
// 对应Runtime.c中Runtime_nativeLoad()
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
// loader == null的情况
...
}
最终调用到jni.h中RegisterNatives()方法。
自定义JNI接口
编写Java层代码
public class HelloJNI {
static {
System.loadLibrary("hello"); // 参数为hello.so文件的文件名
}
// 向Java层暴露
public static void hello() {
native_hello();
}
// 向JNI层暴露
private native void native_hello();
}
编写JNI层代码
com_candy1126xx_jnidemo_hello.h
// 描述对应关系
static JNINativeMethod gMethods[] = {
{"native_hello", "()V", (void *)com_candy1126xx_jnidemo_hello},
};
// 被Java层调用,转调C++层逻辑实现
static void com_candy1126xx_jnidemo_hello()
{
JNIEnv *env = NULL;
g_jvm->AttachCurrentThread(&env, NULL); //g_jvm为JavaVM指针
return say_hello(env); //say_hello()为C++层逻辑实现
}
// 这个方法会在System.loadLibrary()中被调用,即建立对应关系
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env;
if (JNI_OK != vm->GetEnv(reinterpret_cast<void**> (&env),JNI_VERSION_1_4)) {
LOGW("JNI_OnLoad could not get JNI env");
return JNI_ERR;
}
g_jvm = vm;
jclass clazz = env->FindClass("com/candy1126xx/jnidemo/Hello");
if (env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof((methods)[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
编写C++层代码
say_hello.h
extern void say_hello(JNIEnv* env);
say_hello.cpp
void say_hello(JNIEnv* env) {
printf("Hello World!\n");
}
C++调用Java
C++访问Java对象、修改Java对象、调用Java方法的能力来自JNIEnv结构体。定义在jni.h中。
常用方法为:
jclass clazz;
clazz = env->FindClass(类的全限定名);
jfieldID id1 = env->GetFieldID(clazz, 域名, 域的类型签名);
jmethodID id2 = env->GetStaticMethodID(clazz, 方法名, 方法签名);
jobject o = env->GetObjectField(Java对象在C++层的映射, jfieldID);
env->SetObjectField(Java对象在C++层的映射, jfieldID);
env->CallVoidMethod(Java对象在C++层的映射, jmethodID, 参数...);
配置NDK环境
- 下载安装ndk-bundle;
- gradle.properties文件中加android.useDeprecatedNdk=true;
- local.properties文件中添加NDK路径;
- build.gradle文件中defaultConfig节点里添加ndk { moduleName “hello” };
- Rebuild后,so文件在/build/intermediates/ndk/debug/lib下