Java层通过System.load或System.loadLibrary来加载一个so文件,它的定义在Android源码中的路径为libcore/ojluni/src/main/java/java/lang/System.java,执行流程如下:
接下来,让我们具体看下System.loadLibrary这个方法的实现。
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
libcore/ojluni/src/main/java/java/lang/Runtime.java
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + 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 candidates = new ArrayList();
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);
}
以上代码块的主要功能为:
- 若ClassLoader非空,则利用ClassLoader的findLibrary方法来获取library的path;
- 若ClassLoader为空,则根据传递进来的libraryName,获取到library file的name(比如传递“test”进来,经过System.mapLibraryName方法的调用,返回的会是“libtest.so”)。然后再在一个path list(getLibPath函数)(即下面代码中的mLibPaths)中查找到这个library file,并最终确定library 的path;
- 调用nativeLoad这个jni方法来load library。
private volatile String[] mLibPaths = null;
private String[] getLibPaths() {
if (mLibPaths == null) {
synchronized(this) {
if (mLibPaths == null) {
mLibPaths = initLibPaths();
}
}
}
return mLibPaths;
}
然而,这里其实又牵扯出了几个问题:首先,可用的library path都是哪些?这实际上也决定了我们的so文件放在哪些目录下,才可以真正的被load起来。其次,在native层的nativeLoad又是如何实现加载的?下面会对这两个问题,逐一分析介绍。。
So的加载路径 分支1
先来看看当传入的ClassLoader为空的情况(执行System.loadLibrary时并不会发生),那么就需要关注下mLibPaths的赋值,相应代码如下:
private static String[] initLibPaths() {
String javaLibraryPath = System.getProperty("java.library.path");
if (javaLibraryPath == null) {
return EmptyArray.STRING;
}
String[] paths = javaLibraryPath.split(":");
// Add a '/' to the end of each directory so we don't have to do it every time.
for (int i = 0; i < paths.length; ++i) {
if (!paths[i].endsWith("/")) {
paths[i] += "/";
}
}
return paths;
}
这里paths实际上读取自一个system property:java.library.path,直接到System.java下查看初始化代码,它其实是LD_LIBRARY_PATH环境变量的值,具体内容可以查看注释,为"/vendor/lib:/system/lib"
public static String getProperty(String key) {
checkKey(key);
return props.getProperty(key);
}
static {
unchangeableProps = initUnchangeableSystemProperties();
props = initProperties();
private static Properties initProperties() {
Properties p = new PropertiesWithNonOverrideableDefaults(unchangeableProps);
setDefaultChangeableProperties(p);
return p;
}
private static Properties initUnchangeableSystemProperties() {
VMRuntime runtime = VMRuntime.getRuntime();
Properties p = new Properties();
// Set non-static properties.
p.put("java.boot.class.path", runtime.bootClassPath());
p.put("java.class.path", runtime.classPath());
// TODO: does this make any sense? Should we just leave java.home unset?
String javaHome = getenv("JAVA_HOME");
if (javaHome == null) {
javaHome = "/system";
}
p.put("java.home", javaHome);
p.put("java.vm.version", runtime.vmVersion());
try {
StructPasswd passwd = Libcore.os.getpwuid(Libcore.os.getuid());
p.put("user.name", passwd.pw_name);
} catch (ErrnoException exception) {
throw new AssertionError(exception);
}
StructUtsname info = Libcore.os.uname();
p.put("os.arch", info.machine);
if (p.get("os.name") != null && !p.get("os.name").equals(info.sysname)) {
logE("Wrong compile-time assumption for os.name: " + p.get("os.name") + " vs " +
info.sysname);
p.put("os.name", info.sysname);
}
p.put("os.version", info.release);
// Undocumented Android-only properties.
p.put("android.icu.library.version", ICU.getIcuVersion());
p.put("android.icu.unicode.version", ICU.getUnicodeVersion());
p.put("android.icu.cldr.version", ICU.getCldrVersion());
// Property override for ICU4J : this is the location of the ICU4C data. This
// is prioritized over the properties in ICUConfig.properties. The issue with using
// that is that it doesn't play well with jarjar and it needs complicated build rules
// to change its default value.
p.put("android.icu.impl.ICUBinary.dataPath", getenv("ANDROID_ROOT") + "/usr/icu");
parsePropertyAssignments(p, specialProperties());
// Override built-in properties with settings from the command line.
// Note: it is not possible to override hardcoded values.
parsePropertyAssignments(p, runtime.properties());
// Set static hardcoded properties.
// These come last, as they must be guaranteed to agree with what a backend compiler
// may assume when compiling the boot image on Android.
for (String[] pair : AndroidHardcodedSystemProperties.STATIC_PROPERTIES) {
if (p.containsKey(pair[0])) {
logE("Ignoring command line argument: -D" + pair[0]);
}
if (pair[1] == null) {
p.remove(pair[0]);
} else {
p.put(pair[0], pair[1]);
}
}
return p;
}
其中parsePropertyAssignments(p, specialProperties());是通过下面的native方法实现
libcore/ojluni/src/main/native/System.c
static jobjectArray System_specialProperties(JNIEnv* env, jclass ignored) {
jclass stringClass = (*env)->FindClass(env, "java/lang/String");
jobjectArray result = (*env)->NewObjectArray(env, 4, stringClass, NULL);
char path[PATH_MAX];
char* process_path = getcwd(path, sizeof(path));
char user_dir[PATH_MAX + 10] = "user.dir=";
strncat(user_dir, process_path, PATH_MAX);
jstring user_dir_str = (*env)->NewStringUTF(env, user_dir);
if ((*env)->ExceptionCheck(env)) {
return NULL;
}
(*env)->SetObjectArrayElement(env, result, 0, user_dir_str);
if ((*env)->ExceptionCheck(env)) {
return NULL;
}
jstring zlib_str = (*env)->NewStringUTF(env, "android.zlib.version=" ZLIB_VERSION);
if ((*env)->ExceptionCheck(env)) {
return NULL;
}
(*env)->SetObjectArrayElement(env, result, 1, zlib_str);
if ((*env)->ExceptionCheck(env)) {
return NULL;
}
jstring ssl_str = (*env)->NewStringUTF(env, "android.openssl.version=" OPENSSL_VERSION_TEXT);
if ((*env)->ExceptionCheck(env)) {
return NULL;
}
(*env)->SetObjectArrayElement(env, result, 2, ssl_str);
if ((*env)->ExceptionCheck(env)) {
return NULL;
}
const char* library_path = getenv("LD_LIBRARY_PATH");
#if defined(__ANDROID__)
if (library_path == NULL) {
android_get_LD_LIBRARY_PATH(path, sizeof(path));
library_path = path;
}
#endif
if (library_path == NULL) {
library_path = "";
}
char* java_path = malloc(strlen("java.library.path=") + strlen(library_path) + 1);
strcpy(java_path, "java.library.path=");
strcat(java_path, library_path);
jstring java_path_str = (*env)->NewStringUTF(env, java_path);
free((void*)java_path);
if ((*env)->ExceptionCheck(env)) {
return NULL;
}
(*env)->SetObjectArrayElement(env, result, 3, java_path_str);
if ((*env)->ExceptionCheck(env)) {
return NULL;
}
return result;
}
可见LD_LIBRARY_PATH的值就是java.library.path的值
分支2然后我继续分析 ClassLoader.findLibrary() 和 nativeLoad() 方法
这里使用的 ClassLoader 是 PatchClassLoader ,其并没有实现 findLibrary() 方法,而是由其子类 BaseDexClassLoader 实现的,源码如下:
libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
这里交由 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() 去查找 so 库的完整名称,具体的可以看后面针对这个函数的分析,总之这里会从你传入的 native-lib 名称,变成 libnative-lib.so 这样完整的名称。
拿到完整的 so 库的名称之后,会通过遍历 nativeLibraryPathElements 去查找 so 库,那么 nativeLibraryPathElemts 是怎么初始化的?如下:
// Native libraries may exist in both the system and
// application library paths, and we use this search order:
//
// 1. This class loader's library path for application libraries (librarySearchPath):
// 1.1. Native library directories
// 1.2. Path to libraries in apk-files
// 2. The VM's library path from the system property for system libraries
// also known as java.library.path
//
// This order was reversed prior to Gingerbread; see http://b/2933456.
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
suppressedExceptions,
definingContext);
nativeLibraryDirectories 的获取路径是在安装过程,赋值到 ApplicationInfo 中去的,如下:
public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public String nativeLibraryDir;
}
具体细节不分析,总的来说,如果 apk 是系统应用,则 nativeLibraryDir 为 /system/lib/xxx 或者 system/app/xxx/lib。但是对于我们自己的应用来说,这个值为 data/app/包名/lib。
以华为手机,安装抖音极速版为例:
抖音极速版的lib 目录为 /data/data/com.ss.android.ugc.aweme.lite/lib,其中 com.ss.android.ugc.aweme.lite 为包名。
systemNativeLibraryDirectories 是通过静态方法 getProperty() 去获取,这里的代码比较复杂,直接跳过,最终我们获取到的路径是:
bionic/linker/linker.cpp
#if defined(__LP64__)
static const char* const kSystemLibDir = "/system/lib64";
static const char* const kVendorLibDir = "/vendor/lib64";
static const char* const kAsanSystemLibDir = "/data/lib64";
static const char* const kAsanVendorLibDir = "/data/vendor/lib64";
#else
static const char* const kSystemLibDir = "/system/lib";
static const char* const kVendorLibDir = "/vendor/lib";
static const char* const kAsanSystemLibDir = "/data/lib";
static const char* const kAsanVendorLibDir = "/data/vendor/lib";
#endif
static const char* const kDefaultLdPaths[] = {
kSystemLibDir,
kVendorLibDir,
nullptr
};
static const char* const kAsanDefaultLdPaths[] = {
kAsanSystemLibDir,
kSystemLibDir,
kAsanVendorLibDir,
kVendorLibDir,
nullptr
};
最终,调用 NativeLibraryElemt 的 findLibrary()方法,如下:
public String findNativeLibrary(String name) {
maybeInit();
if (zipDir == null) {//这里为 null
String entryPath = new File(path, name).getPath();
if (IoUtils.canOpenReadOnly(entryPath)) {
return entryPath;
}
} else if (urlHandler != null) {
// Having a urlHandler means the element has a zip file.
// In this case Android supports loading the library iff
// it is stored in the zip uncompressed.
String entryName = zipDir + '/' + name;
if (urlHandler.isEntryStored(entryName)) {
return path.getPath() + zipSeparator + entryName;
}
}
return null;
}



