最近开发的应用apk出现了运行时加载so库失败问题,所以顺带研究下android查找和加载so库的过程.
android加载动态库使用java.lang.System类(在libcore/luni/src/main/java/java/lang/System.java这个文件中),它提供了两套方法:
1.直接指定so库详细路径
public static void load(String pathName) {
Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
}
由上面的那段code,可以看到,它的实现非常简单,就只是先调用VMStack.getCallingClassLoader()获取到ClassLoader,然后再把实际要做的事情委托给了Runtime来做而已。
接下来我们再看一下Runtime.load()的实现(在libcore/luni/src/main/java/java/lang/Runtime.java这个文件中):
void load(String filename, ClassLoader loader) {
if (filename == null) {
throw new NullPointerException("library path was null.");
}
String error = nativeLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}
直接调用nativeLoad()这个native方法来load library.System类提供的第一种加载so库方法load()仅仅调用native code直接加载指定路径的so库
.nativeLoad()方法的具体实现后面介绍.
2.指定so库名称,系统自动查找并加载
/**
* Loads and links the library with the specified name. The mapping of the
* specified library name to the full path for loading the library is
* implementation-dependent.
*
* @param libName
* the name of the library to load.
* @throws UnsatisfiedLinkError
* if the library could not be loaded.
*/
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
同样委托Runtime来真正执行操作.
/*
* Loads and links a library without security checks.
*/
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
throw new UnsatisfiedLinkError("Couldn't load " + libraryName + ": " +
"findLibrary returned null");
}
String error = nativeLoad(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 (new File(candidate).exists()) {
String error = nativeLoad(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);
}
正常情况下,VMStack.getCallingClassLoader()获取到ClassLoader不会为空.故这里先分析loader != null分支.此时,首先使用调用类的ClassLoader查找该so库并获取其全路径名;然后同上调用native code直接加载指定路径的so库.
而我们代码加载so库失败的原因就是ClassLoader中没搜索到对应的so库文件.从而运行时异常.先看下java.lang.ClassLoader的默认实现.
protected String findLibrary(String libName) {
return null;
}
基类ClassLoader为空实现.android中它的实际实现类是谁呢?android中直接打印实际运行的ClassLoader的String
05-11 08:18:57.857: V/MainActivity(11556): classLoader = dalvik.system.PathClassLoader[dexPath=/data/app/com.saroll.test.test-1.apk,libraryPath=/data/app-lib/com.test.test-1]
也就是说android应用内部类默认的ClassLoader为dalvik.system.PathClassLoader(位于libcore\dalvik\src\main\java\dalvik\system.PathClassLoader),它其实只是简单的继承BaseDexClassLoader而已,没有任何实际的内容 .直接进入BaseDexClassLoader中看看 findLibrary() 真正的实现( 在libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java这个文件中 ):
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
/**
* Constructs an instance.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; may be {@code null}
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
@Override public String toString() {
return getClass().getName() + "[" + pathList + "]";
}
}