Android中对于so的加载提供了两个方法。
System.loadLibrary("libName");
System.load("pathName");
/**
* See {@link Runtime#load}.
*/
public static void load(String pathName) {
Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
}
/**
* See {@link Runtime#loadLibrary}.
*/
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
其中loadLibrary我们只需要传入so文件的名称就可以了,它会根据传进来的libName,扫描APK内部的nativeLibrary目录,获取并返回内部SO库文件的完整路径filename,然后加载这个so文件。
load方法就为我们外部加载so文件提供了可能,它需要指定一个文件路径,也就是说我们可以使用这个方法来指定我们要加载的so文件的路径来动态的加载so文件。
其实,loadLibrary和load最终都会调用nativeLoad(name, loader, ldLibraryPath)方法,只是因为loadLibrary的参数传入的仅仅是so的文件名,所以,loadLibrary需要首先找到这个文件的路径,然后加载这个so文件。
而load传入的参数是一个文件路径,所以它不需要去寻找这个文件路径,而是直接通过这个路径来加载so文件。
具体我们来看看代码。
先看看System.loadLibrary,这里调用了Runtime的loadLibrary
/*
* Searches for and loads the given shared library using the given ClassLoader.
*/
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
String filename = loader.findLibrary(libraryName);
String error = doLoad(filename, loader);
return;
}
……
}
它执行的是BaseDexClassLoader的findLibrary方法。我们进去看看
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
再看进去DexPathList类
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (File directory : nativeLibraryDirectories) {
File file = new File(directory, fileName);
if (file.exists() && file.isFile() && file.canRead()) {
return file.getPath();
}
}
return null;
}
根据传进来的libName,扫描APK内部的nativeLibrary目录,获取并返回内部SO库文件的完整路径filename。再回到Runtime类,获取filename后调用了“doLoad”方法。
private String doLoad(String name, ClassLoader loader) {
String ldLibraryPath = null;
String dexPath = null;
if (loader == null) {
ldLibraryPath = System.getProperty("java.library.path");
} else if (loader instanceof BaseDexClassLoader) {
BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
ldLibraryPath = dexClassLoader.getLdLibraryPath();
}
synchronized (this) {
return nativeLoad(name, loader, ldLibraryPath);
}
}
调用Native方法“nativeLoad”,通过完整的SO库路径filename,把目标SO库加载进来。
我们再看看System.load方法,调用Runtime的load方法
void load(String absolutePath, ClassLoader loader) {
if (absolutePath == null) {
throw new NullPointerException("absolutePath == null");
}
String error = doLoad(absolutePath, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}
看到没有,它直接调用的就是doLoad方法。也就是说它直接根据传入的完整路径来加载so文件。
所以,如果我们希望动态的加载so文件,我们就可以使用System.load方法来加载。
另外需要注意的是,System.load方法指定的so文件的路径不能为SD卡的路径,因为SD卡等外部存储路径是一种可拆卸的(mounted)不可执行(noexec)的储存媒介,不能直接用来作为可执行文件的运行目录,使用前应该把可执行文件复制到APP内部存储再运行。
所以,假如我们从网络下发一个so文件,下载下来之后,我们首先需要把他复制到应用内部目录下,然后使用System.load方法加载,这样我们就可以调用这个本地方法了。