JNI原理与使用

JNI原理与使用

Posted by candy1126xx on May 28, 2017

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环境

  1. 下载安装ndk-bundle;
  2. gradle.properties文件中加android.useDeprecatedNdk=true;
  3. local.properties文件中添加NDK路径;
  4. build.gradle文件中defaultConfig节点里添加ndk { moduleName “hello” };
  5. Rebuild后,so文件在/build/intermediates/ndk/debug/lib下