本文学习的源码参考AndroidXRef,版本为Lollipop 5.1.0_r1。
在Android开发过程中,我们要想使用JNI机制调用一个native方法时,首先会需要调用一个system的方法,把定义了native方法的库加载进来。今天,就从System.loadLibrary(“XXX”)来详细看一下一个动态库的加载过程。
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
这个loadLibrary是System的一个方法,同时也说明了JNI标准本身就是Java语言的一部分。
可以看到这个方法里实际调用了Runtime的loadLibrary方法。
void loadLibrary(String libraryName, ClassLoader loader) {
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 : mLibPaths) {
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);
}
loadLibrary首先根据传入的loader,调用它的findLibrary(libraryName)
方法去获取要加载的so的文件路径,然后判断一下获得的路径是否为空,不为空,则调用doLoad(filename, loader)
去执行so的加载链接等等过程。
若传入的loader为空,则首先去通过System的mapLibraryName(libraryName)
方法获取so的文件名,然后在一个mLibPaths的变量里面去找这个so文件,并最终拼接形成最终的so文件路径,最后还是调用doLoad(candidate, loader)
去执行so的加载链接过程。
看下doLoad的实现:
private String doLoad(String name, ClassLoader loader) {
String ldLibraryPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
}
synchronized (this) {
return nativeLoad(name, loader, ldLibraryPath);
}
}
注释中提到,Android应用从Zygote进程中创建出来,因此他们没有自定义的LD_LIBRARY_PATH,so文件的默认路径肯定也不在这个变量中;而PathClassLoader知道正确的路径,因此我们可以暂时先不加载依赖库,但是后面需要按依赖顺序加载。我们需要将API添加到Android的动态链接器中,这样我们就可以为当前运行的进程更新所使用的库路径,此处就是将classloader中的路径传递给nativeload。
可以看到doLoad先提取了ldLibraryPath的值,然后和so文件的名字以及loader一起传递给了nativeLoad函数。很显然,