System.loadLibrary
在加载so库的时候,我们最常用的就是
loadLibrary()
这个函数,我们也知道
load()
传递的是so文件的绝对路径,但是
loadLibrary
是如何找到so的呢,今天就去看下这后面发生了什么。
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
到了这里,是不是有点疑惑了
loadLibrary0()
这个函数好理解,
libname
也好理解,
VMStack.getCallingClassLoader()
返回的究竟是哪个ClassLoader呢?其实这里返回的是启动这个
System.loadLibrary
函数调用处所在类的加载器。这样说的有点拗口,举个栗子,在类A中调用了
loadLibrary()
这个函数,那么
VMStack.getCallingClassLoader()
返回的就是类A的classloader。好的,进入
loadLibrary0()
这个函数内部,看看后面发生了什么。
1. so文件的查找
synchronized void loadLibrary0(ClassLoader loader, String 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<String> candidates = new ArrayList<String>();
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);
}
正常情况下,我们传的loader是非空的,很少loader会出现空的情况,故此处我们重点分析load不为空的情况。loader不为空的情况下,首先就调用了loader.findLibrary这个函数,函数的参数是我们传递的so库的名称。这下就要进入loader内部分析了,我们分析DexClassLoader中的findLibrary这个函数。基于android classloader的继承体系,findLibrary这个函数最终在BaseDexClassLoader中实现。
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
findLibrary最终会调用pathList的findLibrary这个函数,pathList是什么鬼?我们看下BaseDexClassLoader函数的构造函数,顺便贴下DexClassLoader的构造函数,理解起来会更容易:
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
可以发现我们在自定义DexClassLoader的时候传递了我们的dexPath(也就是apk文件的路径),optimizedDirectory(odex文件的目录),librarySearchPath(so文件的目录),parent(父加载器);在构造DexPathList对象时传递了dexPath,librarySearchPath,optimizedDirectory。ok,我们到DexPathList里看下findLibrary这个函数的具体实现。
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (Element element : nativeLibraryPathElements) {
String path = element.findNativeLibrary(fileName);
if (path != null) {
return path;
}
}
return null;
}
可以看到首先调用了
System.mapLibraryName(libraryName)
这个函数。这个函数的字面意思感觉是要把我们传递的libraryName映射为另外一个名字,我去!!!感觉这可能就是为什么so文件的名字是libxx.so,但是我们穿xx的时候系统仍然能找到的原因吧。到这里我么就必须要看下
System.mapLibraryName(libraryName)
这个函数的具体实现了。哎,又是一个native实现,智能去扒扒native的源码了。这个方法的具体实现在/libcore/ojluni/src/main/native/System.c中。
JNIEXPORT jstring JNICALL
System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
{
int len;
int prefix_len = (int) strlen(JNI_LIB_PREFIX);
int suffix_len = (int) strlen(JNI_LIB_SUFFIX);
jchar chars[256];
if (libname == NULL) {
JNU_ThrowNullPointerException(env, 0);
return NULL;
}
len = (*env)->GetStringLength(env, libname);
if (len > 240) {
JNU_ThrowIllegalArgumentException(env, "name too long");
return NULL;
}
cpchars(chars, JNI_LIB_PREFIX, prefix_len);
(*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);
len += prefix_len;
cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len);
len += suffix_len;
return (*env)->NewString(env, chars, len);
}
分析这部分源码其实可以发现,就是在libname的前面和后面添加了JNI_LIB_PREFIX和JNI_LIB_SUFFIX,再看下这两个常量是如何定义的。这两个常量在/libcore/ojluni/src/main/native/jvm_md.h中定义:
#define JNI_LIB_PREFIX "lib"
#define JNI_LIB_SUFFIX ".so"
到这里终于弄明白了System.mapLibraryName这个函数的作用,就是讲我们传的xx变为libxx.so。
接下来继续分析DexPathList findLibrary函数的剩余部分:
for (Element element : nativeLibraryPathElements) {
String path = element.findNativeLibrary(fileName);
if (path != null) {
return path;
}
}
nativeLibraryPathElements这个变量我就不贴代码了,这个变量记录了应用查找so文件的路径,这个路径包括系统路径
/vendor/lib/
,
/system/lib/
以及我们创建classload时定义的so文件搜索路径。如果能在任意路径找到需要的so文件,则返回so文件的绝对路径。
以上就是so文件查找的过程。
2. so文件的加载
在第一节中系统找到了so文件的绝对路径,接下来系统会根据so文件的绝对路径加载so文件。
doLoad(filename, loader);
最终加载的函数就是
doLoad(dilename, loader)
,这个函数还没有研究过,后面再说这个函数。到这里大家可能会联想到另一个加载so文件的函数
System.load()
,这个函数相较于
System.loadLibrary()
函数而言,参数值时so文件的绝对路径,不需要进行so文件路径的查找。
好的,so文件的加载过程就到这了。