JNI函数静态注册和动态注册

系统加载lib的方法:

  • 通过JNI_OnLoad,实现动态注册;
  • 如果没有定义JNI_OnLoad,则dvm调用dvmResolveNativeMethod进行动态解析。其实就是保存JNI层函数的函数指针,调用方法时直接使用这个函数指针。

静态注册是Java的Native方法通过方法指针来与JNI进行关联,动态注册可以让Native方法知道它在JNI中对应的方法指针。
JNI中有一种结构用来记录Java的Native方法和JNI方法的关联关系,它就是JNINativeMethod

typedef struct {
    const char* name; // Java方法的名字
    const char* signature; //Java方法的签名信息
    void*  fnPtr; //JNI中对应的方法指针
} JNINativeMethod;

注册流程

  1. 从java代码System.loadLibrary(libName);开始(libcore\ojluni\src\main\java\java\lang);
  2. 根据Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);来到java/lang/Runtime.java里,代码如下
// libcore\ojluni\src\main\java\java\lang\Runtime.java 注意这个Runtime是java核心库里面的和AndroidRuntime无关系
synchronized void loadLibrary0(ClassLoader loader, String libname) {
    if (libname.indexOf((int)File.separatorChar) != -1) {
        throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
    }  // 检测名称合法性
    String libraryName = libname;
    ...
        String error = doLoad(filename, loader);//如果库文件存在,就加载
        if (error != null) {
            throw new UnsatisfiedLinkError(error);
        }//加载库文件失败,抛出异常
        return;
    }
    String filename = System.mapLibraryName(libraryName);
    List<String> candidates = new ArrayList<String>();
    String lastError = null;
    for (String directory : getLibPaths()) { / / getLibPaths()用来获取系统中存放so库的文件路径
         String error = doLoad(candidate, loader); // 加载该库
         if (error == null) {
              return; // We successfully loaded the library. Job done.
          }
          lastError = error;
      }
       if (lastError != null) {
           throw new UnsatisfiedLinkError(lastError);
       }
       throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }

    private String doLoad(String name, ClassLoader loader) {
        if (loader != null && loader instanceof BaseDexClassLoader) {
            BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
            librarySearchPath = dexClassLoader.getLdLibraryPath();
        }
        synchronized (this) {
            return nativeLoad(name, loader, librarySearchPath);// 调用本地方法nativeLoad
        }
    }
  1. 上面doLoad()->nativeLoad(); 本地方法对应的源文件为java_lang_Runtime.cc;
// libcore\ojluni\\src\main\native\Runtime.c  注意这个Runtime是java核心库里面的和AndroidRuntime无关系
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
                   jobject javaLoader, jstring javaLibrarySearchPath)
{    
    // //调用JVM_NativeLoad方法,该方法申明在jvm.h中,实现在OpenjdkJvm.cc中
    return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
}

static JNINativeMethod gMethods[] = {
  NATIVE_METHOD(Runtime, freeMemory, "!()J"),
  NATIVE_METHOD(Runtime, totalMemory, "!()J"),
  NATIVE_METHOD(Runtime, maxMemory, "!()J"),
  NATIVE_METHOD(Runtime, gc, "()V"),
  NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
  NATIVE_METHOD(Runtime, nativeLoad,
                "(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;)"
                    "Ljava/lang/String;"),
};

void register_java_lang_Runtime(JNIEnv* env) {
  jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));
}
  1. art/runtime/openjdkjvm/OpenjdkJvm.cc
// art/runtime/openjdkjvm/OpenjdkJvm.cc
 #include "../../libcore/ojluni/src/main/native/jvm.h"
 //JVM_NativeLoad方法的实现
 JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                 jstring javaFilename,
                                 jobject javaLoader,
                                 jstring javaLibrarySearchPath) {
    ...
    art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
    bool success = vm->LoadNativeLibrary(env,
                                         filename.c_str(),
                                         javaLoader,
                                         javaLibrarySearchPath,
                                         &error_msg); // 调用JavaVMExt的LoadNativeLibrary方法
    if (success) {
      return nullptr;
    }

...
  return env->NewStringUTF(error_msg.c_str());
}
  1. LoadNativeLibrary
//art/runtime/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  jstring library_path,
                                  std::string* error_msg) {
...
sym = library->FindSymbol("JNI_OnLoad", nullptr);
// 在我们要加载so库中查找JNI_OnLoad方法,如果没有就认为是静态注册方式,代表so库加载成功,如果找到JNI_OnLoad就会调用JNI_OnLoad方法,
// JNI_OnLoad方法中一般存放的是方法注册的函数,所以如果采用动态注册就必须要实现JNI_OnLoad方法
if (sym == nullptrr) {
    was_successful = true;
  } else {
typedef int (*JNI_OnLoadFn)(JavaVM*, void*);//定义了一个
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
 // sym是void*类型的,任何一个类型都可以用void*类型进行传递,但是void*类型是不能够调用的,
// 所用在调用之前,需要将void*代表的类型转换为其原来的类型,在这里,把sym重新解释为JNI_OnLoadFn,
// sym指向的是JNI_OnLoad,JNI_OnLoad和JNI_OnLoadFn是相同的类型,其实在我的理解中就是让JNI_OnLoadFn指向JNI_OnLoad函数的地址,
// 这样调用JNI_OnLoadFn就像调用JNI_OnLoad一样
    int version = (*jni_on_load)(this, nullptr);//调用JNI_OnLoad函数,version为JNI_OnLoad函数的返回值
}
...

动态注册和静态注册代码示例

#include <jni.h>
#include <string>

#include<android/log.h>

#define TAG "test-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型

/**
 * 静态注册方式,方法名按照标准写法Java_包名_类名_方法名
 */
extern "C" JNIEXPORT jstring JNICALL Java_com_jianjin33_demo2_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) 
{
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

// 接下来是动态注册的方式,需要实现jni.h中的JNI_OnLoad方法

// 方法名可以随意些
extern "C" JNIEXPORT jstring JNICALL native_stringFromJni2(JNIEnv *env, jclass clazz)
{
    return env->NewStringUTF("动态注册jni函数返回结果");
}

static int registerNativeMethods(JNIEnv* env, const char* className,
                                 JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

#define JNIREG_CLASS "com/jianjin33/demo2/MainActivity" // 指定要注册的类
// JNINativeMethod结构体就是文章开头介绍的
static JNINativeMethod gMethods[] = {
        { "stringFromJni2", "()Ljava/lang/String;", (void*)native_stringFromJni2}, // 绑定
};
static int registerNatives(JNIEnv* env)
{
    if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0])))
        return JNI_FALSE;
    return JNI_TRUE;
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
    LOGI("%s","开始走JNI_OnLoad");
    JNIEnv* env = NULL;
    jint result = -1;
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);

    if (!registerNatives(env)) { // 注册
        return -1;
    }
    result = JNI_VERSION_1_6;
    return result;
}

java代码调用jni

static {
        System.loadLibrary("native-lib");
    }
public native String stringFromJNI(); // 静态注册方式
public native String stringFromJNI2(); // 动态注册方式

两种方式的对比

静态注册:根据函数名来建立Java函数和JNI函数之间的关联关系,要求JNI层函数的名字必须遵守特定的格式,所以书写起来比较长,并且,初次调用本地函数时要根据函数名搜索JNI层函数来建立关联关系,会影响运行效率。
动态注册:扩展性较好,避免了静态注册的不足之处。

参考博客
so库加载System.loadLibrary流程分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容