android 动态库加载原理分析

说到JNI就不得不提到so,关于so的原理我就不赘述了。我们加载so的时候经常会遇到一些问题,现在我就so的加载过程做一个简单的说明
我们使用如下代码System.loadLibrary("hello-jni");来加载libhello-jni.so库,有朋友问这个库应该放在什么地方,其实这个库可以放在两个地方一个是系统是/system/lib下面,另外一个是/data/data/com.example.hellojni/lib下面,通常情况下前者是没有写入权限的。好了,现在我们分析android是如何加载so的
1,我们跟踪到./dalvik/libcore/luni-kernel/src/main/java/java/lang/System.java中的loadLibrary他调用了
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
2,./dalvik/libcore/luni-kernel/src/main/java/java/lang/Runtime.java这个类,我们看到调用了nativeLoad方法,我们继续跟踪下去
3,java_lang_Runtime.c中我们找到了这个 nativeLoad方法,对应的Dalvik_java_lang_Runtime_nativeLoad方法,我们继续跟踪
4,bool dvmLoadNativeCode(const char* pathName, Object* classLoader)
{
    SharedLib* pEntry;
    void* handle;
    bool verbose;

    /* reduce noise by not chattering about system libraries */
//这里我们先去比较是否是加载system/lib下面的so
    verbose = strncmp(pathName, "/system", sizeof("/system")-1) != 0;

    if (verbose)
        LOGD("Trying to load lib %s %p\n", pathName, classLoader);

    /*
     * See if we've already loaded it.  If we have, and the class loader
     * matches, return successfully without doing anything.
     */
//查看当前so是否已经加载,这里提醒我们如果更新了so之后,必须重新启动当前虚拟机,否则不起作用。
    pEntry = findSharedLibEntry(pathName);
    if (pEntry != NULL) {
        if (pEntry->classLoader != classLoader) {
            LOGW("Shared lib '%s' already opened by CL %p; can't open in %p\n",
                pathName, pEntry->classLoader, classLoader);
            return false;
        }
        if (verbose) {
            LOGD("Shared lib '%s' already loaded in same CL %p\n",
                pathName, classLoader);
        }
        if (!checkOnLoadResult(pEntry))
            return false;
        return true;
    }

  
    Thread* self = dvmThreadSelf();
    int oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
    handle = dlopen(pathName, RTLD_LAZY);
    dvmChangeStatus(self, oldStatus);

    if (handle == NULL) {
        LOGI("Unable to dlopen(%s): %s\n", pathName, dlerror());
        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* pActualEntry = addSharedLibEntry(pNewEntry);

    if (pNewEntry != pActualEntry) {
        LOGI("WOW: we lost a race to add a shared lib (%s CL=%p)\n",
            pathName, classLoader);
        freeSharedLibEntry(pNewEntry);
        return checkOnLoadResult(pActualEntry);
    } else {
        if (verbose)
            LOGD("Added shared lib %s %p\n", pathName, classLoader);

        bool result = true;
        void* vonLoad;
        int version;
//此处会坚持JNI_OnLoad方法,所以ndk提供的hello-jni里面应该不算是标准的android jni方式
        vonLoad = dlsym(handle, "JNI_OnLoad");
        if (vonLoad == NULL) {
            LOGD("No JNI_OnLoad found in %s %p, skipping init\n",
                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 = vonLoad;
            Object* prevOverride = self->classLoaderOverride;

            self->classLoaderOverride = classLoader;
            oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
            LOGV("+++ calling JNI_OnLoad(%s)\n", pathName);
            version = (*func)(gDvm.vmList, NULL);
            dvmChangeStatus(self, oldStatus);
            self->classLoaderOverride = prevOverride;

//此处我们坚持JNI_OnLoad返回的so版本号
            if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 &&
                version != JNI_VERSION_1_6)
            {
                LOGW("JNI_OnLoad returned bad version (%d) in %s %p\n",
                    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 {
                LOGV("+++ finished JNI_OnLoad %s\n", 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;
    }
}

自此,我们完全分析了android虚拟机是如何加载so的,另外给大家的建议是,最好不要把so随便放在system/lib下面,因为据我的经验虚拟机优先加载这个下面的库,我没有从理论上做过分析,另外这个目录下面基本上没有写权限,所以我们自己开发的程序如果用到动态库,还是放在自己的目录下面比较好。

android 动态库加载原理分析(原创,转载请注明出处)

from:http://www.eoeandroid.com/blog-21517-1792.html

你可能感兴趣的:(android)