说到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