JNI方法注册及加载原理分析

JNI方法注册方式分为动态与静态注册。

1.函数静态注册

1.1JNI层函数格式:Java_包名_类名 _方法名

下划线隔开,通过javah生成带签名的函数,然后去实现这些函数,这种也是官方推荐的方式。
Java层方法声明成native方法:

public native String signture(String sig);

JNI层对应的方法:

JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_signture
        (JNIEnv *env, jobject jobject1, jstring jstring1) {
    //md5加密
    const char *jcstr = (env)->GetStringUTFChars(jstring1, 0);
    MD5 md5;
    md5.update(jcstr);
    string jstring2 = md5.toString();

    //package_name
    jstring packName = getPackname(env, jobject1, jobject1);
    const char *c = env->GetStringUTFChars(packName, 0);
    LOGE("getPackageName: %s", c);

    //signature
    jstring signatures = getSignature(env, jobject1, jobject1);
    const char *signaturesc = env->GetStringUTFChars(signatures, 0);
    LOGE("signatures: %s", signaturesc);
    return env->NewStringUTF(jstring2.c_str());
}

1.2 优缺点:

优点:
java与jni方法对应清晰
缺点:
必须按照函数规则,方法名很长
运行时查找函数效率低

2.函数动态注册

2.1 动态注册

定义的native方法

public native String stringFromJNI();
public native String signture(String sig);
int RegisterNatives(JNIEnv *env) {
    jclass clazz = env->FindClass("jni/chowen/com/nativeapp/MainActivity");
    if (clazz == NULL) {
        LOGE("con't find class: jni/chowen/com/nativeapp/MainActivity");
        return JNI_ERR;
    }
    // 动态注册的函数数组
    JNINativeMethod methods_MainActivity[] = {
            {"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
            {"signture",           "(Ljava/lang/String;)V",                (void *) signture}
    };
    LOGE("can find class: jni/chowen/com/nativeapp/MainActivity %d >>> %d", sizeof(methods_MainActivity), sizeof(methods_MainActivity[0]));
    // int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);
   // 实际注册的函数,clazz:对象,methods_MainActivity:函数数组
   //sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]): 方法个数
    return env->RegisterNatives(clazz, methods_MainActivity,
                                sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
}

2.2RegisterNatives函数说明:

env->RegisterNatives(clazz, methods_MainActivity,
                                sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));

RegisterNatives为函数动态注册的方法
参数:
clazz:java对象
methods_MainActivity:函数数组
sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]):计算函数个数

2.3JNINativeMethod说明:

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

JNINativeMethod为一个结构体。定义在jni.h头文件中。
const char* name:java层native函数名称
const char* signature:函数签名描述
void* fnPtr:Native对应的函数指针

那么动态注册的这个方法RegisterNatives 调用时机是什么时候呢?下来介绍下JNI_OnLoad(JavaVM* vm, void* reserved)函数。那么问题来了,JNI_OnLoad函数什么时候会被调用呢?
原来是当通过System.loadLibrary()加载so的时候,VM会立即调用JNI_OnLoad函数。所以一些初始化的工作可以放到JNI_OnLoad函数里去完成。所以动态注册就在这个函数里完成java native方法与so函数之间的绑定关系。

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    LOGE("JNI_OnLoad RegisterNatives JNI_OnLoad");
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    jint result = RegisterNatives(env);
    LOGE("RegisterNatives result: %d", result);
    return JNI_VERSION_1_6;
}

动态注册在framework层使用的多,而静态注册一般我们平时开发在上层用的较多,也是官方推荐的方式。

3.函数如何加载

3.1 静态注册的函数映射

用javah风格的代码,则dvm调用dvmResolveNativeMethod进行动态延迟解析,直到需要调用的时候才会解析。
延迟加载

void dvmResolveNativeMethod(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
{
    ClassObject* clazz = method->clazz;
    /*
     * If this is a static method, it could be called before the class
     * has been initialized.
     */
    if (dvmIsStaticMethod(method)) {
        if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
            assert(dvmCheckException(dvmThreadSelf()));
            return;
        }
    } else {
        assert(dvmIsClassInitialized(clazz) ||
               dvmIsClassInitializing(clazz));
    }
    /* start with our internal-native methods */
    //在内部本地方法表中查询
    DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method);
    if (infunc != NULL) {
        /* resolution always gets the same answer, so no race here */
        IF_LOGVV() {
            char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
            LOGVV("+++ resolved native %s.%s %s, invoking",
                clazz->descriptor, method->name, desc);
            free(desc);
        }
        if (dvmIsSynchronizedMethod(method)) {
            ALOGE("ERROR: internal-native can't be declared 'synchronized'");
            ALOGE("Failing on %s.%s", method->clazz->descriptor, method->name);
            dvmAbort();     // harsh, but this is VM-internal problem
        }
        DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc;
        dvmSetNativeFunc((Method*) method, dfunc, NULL);
        dfunc(args, pResult, method, self);
        return;
    }
    /* now scan any DLLs we have loaded for JNI signatures */
   // 去已加载的动态库中查询
    void* func = lookupSharedLibMethod(method); 
    if (func != NULL) {
        /* found it, point it at the JNI bridge and then call it */
        dvmUseJNIBridge((Method*) method, func);
        (*method->nativeFunc)(args, pResult, method, self);
        return;
    }
    IF_ALOGW() {
        char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
        ALOGW("No implementation found for native %s.%s:%s",
            clazz->descriptor, method->name, desc);
        free(desc);
    }
    dvmThrowUnsatisfiedLinkError("Native method not found", method);
}

去以加载的动态库中查询,这个时候会调用findMethodInLib函数,这个函数就是动态库中没有重载JNI_OnLoad 函数。然后在动态库中查询注册函数。

/*
 * (This is a dvmHashForeach callback.)
 *
 * Search for a matching method in this shared library.
 *
 * TODO: we may want to skip libraries for which JNI_OnLoad failed.
 */
static int findMethodInLib(void* vlib, void* vmethod)
{
    const SharedLib* pLib = (const SharedLib*) vlib;
    const Method* meth = (const Method*) vmethod;
    char* preMangleCM = NULL;
    char* mangleCM = NULL;
    char* mangleSig = NULL;
    char* mangleCMSig = NULL;
    void* func = NULL;
    int len;
    if (meth->clazz->classLoader != pLib->classLoader) {
        ALOGV("+++ not scanning '%s' for '%s' (wrong CL)",
            pLib->pathName, meth->name);
        return 0;
    } else
        ALOGV("+++ scanning '%s' for '%s'", pLib->pathName, meth->name);
    /*
     * First, we try it without the signature.
     */
    preMangleCM =
        createJniNameString(meth->clazz->descriptor, meth->name, &len);
    if (preMangleCM == NULL)
        goto bail;
     // java与native进行替换,替换成javah风格函数
    mangleCM = mangleString(preMangleCM, len);
    if (mangleCM == NULL)
        goto bail;
    ALOGV("+++ calling dlsym(%s)", mangleCM);
    func = dlsym(pLib->handle, mangleCM);
    if (func == NULL) {
        mangleSig =
            createMangledSignature(&meth->prototype);
        if (mangleSig == NULL)
            goto bail;
        mangleCMSig = (char*) malloc(strlen(mangleCM) + strlen(mangleSig) +3);
        if (mangleCMSig == NULL)
            goto bail;
        sprintf(mangleCMSig, "%s__%s", mangleCM, mangleSig);
        ALOGV("+++ calling dlsym(%s)", mangleCMSig);
        func = dlsym(pLib->handle, mangleCMSig);
        if (func != NULL) {
            ALOGV("Found '%s' with dlsym", mangleCMSig);
        }
    } else {
        ALOGV("Found '%s' with dlsym", mangleCM);
    }
bail:
    free(preMangleCM);
    free(mangleCM);
    free(mangleSig);
    free(mangleCMSig);
    return (int) func;
}

mangleString(preMangleCM, len): java与native进行替换,替换成javah生成的风格函数。
总结几步:
1.vm调用 dvmResolveNativeMethod函数
2.void* func = lookupSharedLibMethod(method) 去已加载的动态库中查询函数的实现。
3.查询到然后执行下面函数将JNI函数地址保持到ClassObject中
dvmUseJNIBridge((Method) method, func)
(
method->nativeFunc)(args, pResult, method, self)
4.最后将函数添加到ClassObject结构体中的directMethods结构体成员中。

3.2 动态注册的函数映射

SO加载及动态注册流程图:
JNI方法注册及加载原理分析_第1张图片
image.png
3.2.1 Java层与native层方法是如何注册并映射的
System.loadLibrary
----Runtime.loadLibrary0
------Runtime.doLoad
--------Runtime.nativeLoad

SO加载过程如下:
System.loadLibrary

@CallerSensitive
    public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
    }

Runtime.loadLibrary0

@CallerSensitive
    public void loadLibrary(String libname) {
        loadLibrary0(VMStack.getCallingClassLoader(), libname);
    }

Runtime.doLoad

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;
        if (loader != null) {
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                // It's not necessarily true that the ClassLoader used
                // System.mapLibraryName, but the default setup does, and it's
                // misleading to say we didn't find "libMyLibrary.so" when we
                // actually searched for "liblibMyLibrary.so.so".
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                               System.mapLibraryName(libraryName) + "\"");
            }
            String error = doLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }

        String filename = System.mapLibraryName(libraryName);
        List candidates = new ArrayList();
        String lastError = null;
        for (String directory : getLibPaths()) {
            String candidate = directory + filename;
            candidates.add(candidate);

            if (IoUtils.canOpenReadOnly(candidate)) {
                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);
    }

Runtime.nativeLoad

private String doLoad(String name, ClassLoader loader) {
        String librarySearchPath = null;
        if (loader != null && loader instanceof BaseDexClassLoader) {
            BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
            librarySearchPath = dexClassLoader.getLdLibraryPath();
        }
        // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
        // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
        // internal natives.
        synchronized (this) {
            return nativeLoad(name, loader, librarySearchPath);
        }
    }

有过JNI开发的同学应该熟悉System.loadLibrary这个方法,函数的注册映射就是从这里开始的。最后调用nativeLoad这个native方法,对应jni的函数java_lang_Runtime.cpp中Dalvik_java_lang_Runtime_nativeLoad然后调用vm/ Native.cpp中dvmLoadNativeCode函数

// platform/dalvik2/vm/native/java_lang_Runtime.cpp
static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
    JValue* pResult) {
    StringObject* fileNameObj = (StringObject*) args[0];
    Object* classLoader = (Object*) args[1];
    StringObject* ldLibraryPathObj = (StringObject*) args[2];
    ...
    bool success = dvmLoadNativeCode(fileName, classLoader, &reason);
}
//platform/dalvik2/+/refs/heads/master/vm/Native.cpp
bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
        char** detail)
{
    SharedLib* pEntry;
    void* handle;
    bool verbose;
    /* reduce noise by not chattering about system libraries */
    verbose = !!strncmp(pathName, "/system", sizeof("/system")-1);
    verbose = verbose && !!strncmp(pathName, "/vendor", sizeof("/vendor")-1);
    if (verbose)
        ALOGD("Trying to load lib %s %p", pathName, classLoader);
    *detail = NULL;
    /*
     * See if we've already loaded it.  If we have, and the class loader
     * matches, return successfully without doing anything.
     */
    // 检查是否已经加载过
    pEntry = findSharedLibEntry(pathName);
    if (pEntry != NULL) {
     //classLoader是否相同,一个so只能由一个classLoader加载
        if (pEntry->classLoader != classLoader) {
            ALOGW("Shared lib '%s' already opened by CL %p; can't open in %p",
                pathName, pEntry->classLoader, classLoader);
            return false;
        }
        if (verbose) {
            ALOGD("Shared lib '%s' already loaded in same CL %p",
                pathName, classLoader);
        }
        if (!checkOnLoadResult(pEntry))
            return false;
        return true;
    }
Thread* self = dvmThreadSelf();
    ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
 // 打开so动态库文件,创建一个handle
    handle = dlopen(pathName, RTLD_LAZY);
    dvmChangeStatus(self, oldStatus);
    if (handle == NULL) {
        *detail = strdup(dlerror());
        ALOGE("dlopen(\"%s\") failed: %s", pathName, *detail);
        return false;
    }
    /* create a new entry */
    SharedLib* pNewEntry;
    pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));
    pNewEntry->pathName = strdup(pathName);
    pNewEntry->handle = handle;
    pNewEntry->classLoader = classLoader;
    dvmInitMutex(&pNewEntry->onLoadLock);
    pthread_cond_init(&pNewEntry->onLoadCond, NULL);
    pNewEntry->onLoadThreadId = self->threadId;
    /* try to add it to the list */
    //将SharedLib添加到哈希表中
    SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
    if (pNewEntry != pActualEntry) {
        ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)",
            pathName, classLoader);
        freeSharedLibEntry(pNewEntry);
        return checkOnLoadResult(pActualEntry);
    } else {
        if (verbose)
            ALOGD("Added shared lib %s %p", pathName, classLoader);
        bool result = true;
        void* vonLoad;
        int version;
       // 查看so是否有JNI_OnLoad函数,有并调用这个函数
        vonLoad = dlsym(handle, "JNI_OnLoad");
        // vonLoad==NULL 说明so里没有JNI_OnLoad方法
        if (vonLoad == NULL) {
            ALOGD("No JNI_OnLoad found in %s %p, skipping init",
                pathName, classLoader);
        } else {
            /*
             * Call JNI_OnLoad.  We have to override the current class
             * loader, which will always be "null" since the stuff at the
             * top of the stack is around Runtime.loadLibrary().  (See
             * the comments in the JNI FindClass function.)
             */
            OnLoadFunc func = (OnLoadFunc)vonLoad;
            Object* prevOverride = self->classLoaderOverride;
            self->classLoaderOverride = classLoader;
            oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
            if (gDvm.verboseJni) {
                ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);
            }
           // 加载JNI_OnLoad方法
            version = (*func)(gDvmJni.jniVm, NULL);
            dvmChangeStatus(self, oldStatus);
            self->classLoaderOverride = prevOverride;
            if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 &&
                version != JNI_VERSION_1_6)
            {
                ALOGW("JNI_OnLoad returned bad version (%d) in %s %p",
                    version, pathName, classLoader);
                /*
                 * It's unwise to call dlclose() here, but we can mark it
                 * as bad and ensure that future load attempts will fail.
                 *
                 * We don't know how far JNI_OnLoad got, so there could
                 * be some partially-initialized stuff accessible through
                 * newly-registered native method calls.  We could try to
                 * unregister them, but that doesn't seem worthwhile.
                 */
                result = false;
            } else {
                if (gDvm.verboseJni) {
                    ALOGI("[Returned from JNI_OnLoad for \"%s\"]", pathName);
                }
            }
        }
        if (result)
            pNewEntry->onLoadResult = kOnLoadOkay;
        else
            pNewEntry->onLoadResult = kOnLoadFailed;
        pNewEntry->onLoadThreadId = 0;
        /*
         * Broadcast a wakeup to anybody sleeping on the condition variable.
         */
        dvmLockMutex(&pNewEntry->onLoadLock);
        pthread_cond_broadcast(&pNewEntry->onLoadCond);
        dvmUnlockMutex(&pNewEntry->onLoadLock);
        return result;
    }
}

以上步骤:
1.检查so是否已经加载过,如果是检查classLoader是否相同,一个so只能由一个classLoader加载
2.打开so动态库文件,创建一个handle
3.将SharedLib添加到哈希表中
4.查看so是否有JNI_OnLoad函数,有并调用这个函数,vonLoad==NULL 说明so里没有JNI_OnLoad方法
5.有JNI_OnLoad函数这时就会去加载JNI_OnLoad方法

3.2.2 JNI_OnLoad过程

JNI_OnLoad
---RegisterNatives
--------dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
const char* signature, void* fnPtr)
--------------dvmUseJNIBridge(method, fnPtr)-> (method->nativeFunc = func)
最终将JNI函数起始地址保存到ClassObject结构体directMethods中。

4.ClassObject结构体

struct ClassObject : Object {  
    /* static, private, and  methods */  
    int             directMethodCount;  
    Method*         directMethods;  
  
    /* virtual methods defined in this class; invoked through vtable */  
    int             virtualMethodCount;  
    Method*         virtualMethods;  
}  

That's all

你可能感兴趣的:(JNI方法注册及加载原理分析)