System.loadLibrary()是我们在使用Java的JNI机制时,会用到的一个非常重要的函数,它的作用即是把实现了我们在Java code中声明的native方法的那个libraryload进来,或者load其他什么动态连接库。
算是处于好奇吧,我们可以看一下这个方法它的实现,即执行流程。(下面分析的那些code,来自于Android 4.2.2 aosp版。)先看一下这个方法的code(在libcore/luni/src/main/java/java/lang/System.java这个文件中):
02 | * Loads and links the library with the specified name. The mapping of the |
03 | * specified library name to the full path for loading the library is |
04 | * implementation-dependent. |
07 | * the name of the library to load. |
08 | * @throws UnsatisfiedLinkError |
09 | * if the library could no<span style="color:#003399;"></span>t be loaded. |
11 | public static void loadLibrary(String libName) { |
12 | Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader()); |
由上面的那段code,可以看到,它的实现非常简单,就只是先调用VMStack.getCallingClassLoader()获取到ClassLoader,然后再把实际要做的事情委托给了Runtime来做而已。接下来我们再看一下Runtime.loadLibrary()的实现(在libcore/luni/src/main/java/java/lang/Runtime.java这个文件中):
04 | void loadLibrary(String libraryName, ClassLoader loader) { |
06 | String filename = loader.findLibrary(libraryName); |
07 | if (filename == null ) { |
08 | throw new UnsatisfiedLinkError( "Couldn't load " + libraryName |
09 | + " from loader " + loader |
10 | + ": findLibrary returned null" ); |
12 | String error = nativeLoad(filename, loader); |
14 | throw new UnsatisfiedLinkError(error); |
19 | String filename = System.mapLibraryName(libraryName); |
20 | List<String> candidates = new ArrayList<String>(); |
21 | String lastError = null ; |
22 | for (String directory : mLibPaths) { |
23 | String candidate = directory + filename; |
24 | candidates.add(candidate); |
25 | if ( new File(candidate).exists()) { |
26 | String error = nativeLoad(candidate, loader); |
34 | if (lastError != null ) { |
35 | throw new UnsatisfiedLinkError(lastError); |
37 | throw new UnsatisfiedLinkError( "Library " + libraryName + " not found; tried " + candidates); |
由上面的那段code,我们看到,loadLibrary()可以被看作是一个2步走的过程:
- 获取到library path。对于这一点,上面的那个函数,依据于所传递的ClassLoader的不同,会有两种不同的方法。如果ClassLoader非空,则会利用ClassLoader的findLibrary()方法来获取library的path。而如果ClassLoader为空,则会首先依据传递进来的library name,获取到library file的name,比如传递“hello”进来,它的library file name,经过System.mapLibraryName(libraryName)将会是“libhello.so”;然后再在一个path list(即上面那段code中的mLibPaths)中查找到这个library file,并最终确定library 的path。
- 调用nativeLoad()这个native方法来load library
这段code,又牵出几个问题,首先,可用的library path都是哪些,这实际上也决定了,我们的so文件放在哪些folder下,才可以被真正load起来?其次,在native层load library的过程,又实际做了什么事情?下面会对这两个问题,一一的作出解答。
系统的library path
我们由简单到复杂的来看这个问题。先来看一下,在传入的ClassLoader为空的情况(尽管我们知道,在System.loadLibrary()这个case下不会发生),前面Runtime.loadLibrary()的实现中那个mLibPaths的初始化的过程,在Runtime的构造函数中,如下:
02 | * Prevent this class from being instantiated. |
05 | String pathList = System.getProperty( "java.library.path" , "." ); |
06 | String pathSep = System.getProperty( "path.separator" , ":" ); |
07 | String fileSep = System.getProperty( "file.separator" , "/" ); |
09 | mLibPaths = pathList.split(pathSep); |
13 | for ( int i = 0 ; i < mLibPaths.length; i++) { |
14 | if (!mLibPaths[i].endsWith(fileSep)) { |
15 | mLibPaths[i] += fileSep; |
可以看到,那个library path list实际上读取自一个system property。那在android系统中,这个system property的实际内容又是什么呢?dump这些内容出来,就像下面这样:
1 | 05-11 07:51:40.974: V/QRCodeActivity(11081): pathList = /vendor/lib:/system/lib |
2 | 05-11 07:51:40.974: V/QRCodeActivity(11081): pathSep = : |
3 | 05-11 07:51:40.974: V/QRCodeActivity(11081): fileSep = / |
然后是传入的ClassLoader非空的情况,ClassLoader的findLibrary()方法的执行过程。首先看一下它的实现(在libcore/luni/src/main/java/java/lang/ClassLoader.java这个文件中):
02 | * Returns the absolute path of the native library with the specified name, |
03 | * or {@code null}. If this method returns {@code null} then the virtual |
04 | * machine searches the directories specified by the system property |
05 | * "java.library.path". |
07 | * This implementation always returns {@code null}. |
11 | * the name of the library to find. |
12 | * @return the absolute path of the library. |
14 | protected String findLibrary(String libName) { |
竟然是一个空函数。那系统中实际运行的ClassLoader就是这个吗?我们可以做一个小小的实验,打印系统中实际运行的ClassLoader的String:
1 | ClassLoader classLoader = getClassLoader(); |
2 | Log.v(TAG, "classLoader = " + classLoader.toString()); |
在Galaxy Nexus上执行的结果如下:
1 | 05-11 08:18:57.857: V/QRCodeActivity(11556): classLoader = dalvik.system.PathClassLoader[dexPath=/data/app/com.qrcode.qrcode-1.apk,libraryPath=/data/app-lib/com.qrcode.qrcode-1] |
看到了吧,android系统中的
ClassLoader真正的实现
在dalvik的dalvik.system.PathClassLoader。打开libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java来看
PathClassLoader这个class
的实现,可以看到,就只是简单的继承
BaseDexClassLoader而已,没有任何实际的内容
。接下来我们就来看一下
BaseDexClassLoader中
那个
findLibrary()
真正的实现(
在libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java这个文件中
):
2 | public String findLibrary(String name) { |
3 | return pathList.findLibrary(name); |
这个方法看上去倒挺简单,不用多做解释。然后来看那个pathList的初始化的过程,在BaseDexClassLoader的构造函数里:
02 | * Constructs an instance. |
04 | * @param dexPath the list of jar/apk files containing classes and |
05 | * resources, delimited by {@code File.pathSeparator}, which |
06 | * defaults to {@code ":"} on Android |
07 | * @param optimizedDirectory directory where optimized dex files |
08 | * should be written; may be {@code null} |
09 | * @param libraryPath the list of directories containing native |
10 | * libraries, delimited by {@code File.pathSeparator}; may be |
12 | * @param parent the parent class loader |
14 | public BaseDexClassLoader(String dexPath, File optimizedDirectory, |
15 | String libraryPath, ClassLoader parent) { |
18 | this .originalPath = dexPath; |
19 | this .originalLibraryPath = libraryPath; |
21 | new DexPathList( this , dexPath, libraryPath, optimizedDirectory); |
BaseDexClassLoader的构造函数也不用多做解释吧。然后是DexPathList的构造函数:
02 | * Constructs an instance. |
04 | * @param definingContext the context in which any as-yet unresolved |
05 | * classes should be defined |
06 | * @param dexPath list of dex/resource path elements, separated by |
07 | * {@code File.pathSeparator} |
08 | * @param libraryPath list of native library directory path elements, |
09 | * separated by {@code File.pathSeparator} |
10 | * @param optimizedDirectory directory where optimized {@code .dex} files |
11 | * should be found and written to, or {@code null} to use the default |
12 | * system directory for same |
14 | public DexPathList(ClassLoader definingContext, String dexPath, |
15 | String libraryPath, File optimizedDirectory) { |
16 | if (definingContext == null ) { |
17 | throw new NullPointerException( "definingContext == null" ); |
20 | if (dexPath == null ) { |
21 | throw new NullPointerException( "dexPath == null" ); |
24 | if (optimizedDirectory != null ) { |
25 | if (!optimizedDirectory.exists()) { |
26 | throw new IllegalArgumentException( |
27 | "optimizedDirectory doesn't exist: " |
28 | + optimizedDirectory); |
31 | if (!(optimizedDirectory.canRead() |
32 | && optimizedDirectory.canWrite())) { |
33 | throw new IllegalArgumentException( |
34 | "optimizedDirectory not readable/writable: " |
35 | + optimizedDirectory); |
39 | this .definingContext = definingContext; |
41 | makeDexElements(splitDexPath(dexPath), optimizedDirectory); |
42 | this .nativeLibraryDirectories = splitLibraryPath(libraryPath); |
关于我们的library path的问题,可以只关注最后的那个splitLibraryPath(),这个地方,实际上即是把传进来的libraryPath 又丢给splitLibraryPath来获取library path 的list。可以看一下DexPathList.splitLibraryPath()的实现:
02 | * Splits the given library directory path string into elements |
03 | * using the path separator ({@code File.pathSeparator}, which |
04 | * defaults to {@code ":"} on Android, appending on the elements |
05 | * from the system library path, and pruning out any elements that |
06 | * do not refer to existing and readable directories. |
08 | private static File[] splitLibraryPath(String path) { |
20 | ArrayList<File> result = splitPaths( |
21 | path, System.getProperty( "java.library.path" , "." ), true ); |
22 | return result.toArray( new File[result.size()]); |
这个地方,是在用两个部分的library path list来由splitPaths构造最终的那个path list,一个部分是,传进来的library path,另外一个部分是,像我们前面看到的那个,是system property。然后再来看一下DexPathList.splitPaths()的实现:
02 | * Splits the given path strings into file elements using the path |
03 | * separator, combining the results and filtering out elements |
04 | * that don't exist, aren't readable, or aren't either a regular |
05 | * file or a directory (as specified). Either string may be empty |
06 | * or {@code null}, in which case it is ignored. If both strings |
07 | * are empty or {@code null}, or all elements get pruned out, then |
08 | * this returns a zero-element list. |
10 | private static ArrayList<File> splitPaths(String path1, String path2, |
11 | boolean wantDirectories) { |
12 | ArrayList<File> result = new ArrayList<File>(); |
14 | splitAndAdd(path1, wantDirectories, result); |
15 | splitAndAdd(path2, wantDirectories, result); |
总结一下,ClassLoader的那个findLibrary()实际上会在两个部分的folder中去寻找System.loadLibrary()要load的那个library,一个部分是,构造ClassLoader时,传进来的那个library path,即是app folder,另外一个部分是system property。在android系统中,查找要load的library,实际上会在如下3个folder中进行:
- /vendor/lib
- /system/lib
- /data/app-lib/com.qrcode.qrcode-1
上面第3个item只是一个例子,每一个app,它的那个app library path的最后一个部分都会是特定于那个app的。至于说,构造BaseDexClassLoader时的那个libraryPath 到底是怎么来的,那可能就会牵扯到android本身更复杂的一些过程了,在此不再做更详细的说明。
Native 层load library的过程
然后来看一下native层,把so文件load起的过程,先来一下nativeLoad()这个函数的实现(在JellyBean/dalvik/vm/native/java_lang_Runtime.cpp这个文件中):
08 | static void Dalvik_java_lang_Runtime_nativeLoad( const u4* args, |
11 | StringObject* fileNameObj = (StringObject*) args[0]; |
12 | Object* classLoader = (Object*) args[1]; |
13 | char * fileName = NULL; |
14 | StringObject* result = NULL; |
18 | assert (fileNameObj != NULL); |
19 | fileName = dvmCreateCstrFromString(fileNameObj); |
21 | success = dvmLoadNativeCode(fileName, classLoader, &reason); |
23 | const char * msg = (reason != NULL) ? reason : "unknown failure" ; |
24 | result = dvmCreateStringFromCstr(msg); |
25 | dvmReleaseTrackedAlloc((Object*) result, NULL); |
可以看到,nativeLoad()实际上只是完成了两件事情,第一,是调用dvmCreateCstrFromString()将Java 的library path String 转换到native的String,然后将这个path传给dvmLoadNativeCode()做load,dvmLoadNativeCode()这个函数的实现在dalvik/vm/Native.cpp中,如下:
018 | bool dvmLoadNativeCode( const char * pathName, Object* classLoader, |
026 | verbose = !! strncmp (pathName, "/system" , sizeof ( "/system" )-1); |
027 | verbose = verbose && !! strncmp (pathName, "/vendor" , sizeof ( "/vendor" )-1); |
030 | ALOGD( "Trying to load lib %s %p" , pathName, classLoader); |
038 | pEntry = findSharedLibEntry(pathName); |
039 | if (pEntry != NULL) { |
040 | if (pEntry->classLoader != classLoader) { |
041 | ALOGW( "Shared lib '%s' already opened by CL %p; can't open in %p" , |
042 | pathName, pEntry->classLoader, classLoader); |
046 | ALOGD( "Shared lib '%s' already loaded in same CL %p" , |
047 | pathName, classLoader); |
049 | if (!checkOnLoadResult(pEntry)) |
081 | Thread* self = dvmThreadSelf(); |
082 | ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); |
083 | handle = dlopen(pathName, RTLD_LAZY); |
084 | dvmChangeStatus(self, oldStatus); |
086 | if (handle == NULL) { |
087 | *detail = strdup(dlerror()); |
088 | ALOGE( "dlopen(\"%s\") failed: %s" , pathName, *detail); |
093 | SharedLib* pNewEntry; |
094 | pNewEntry = (SharedLib*) calloc (1, sizeof (SharedLib)); |
095 | pNewEntry->pathName = strdup(pathName); |
096 | pNewEntry->handle = handle; |
097 | pNewEntry->classLoader = classLoader; |
098 | dvmInitMutex(&pNewEntry->onLoadLock); |
099 | pthread_cond_init(&pNewEntry->onLoadCond, NULL); |
100 | pNewEntry->onLoadThreadId = self->threadId; |
103 | SharedLib* pActualEntry = addSharedLibEntry(pNewEntry); |
105 | if (pNewEntry != pActualEntry) { |
106 | ALOGI( "WOW: we lost a race to add a shared lib (%s CL=%p)" , |
107 | pathName, classLoader); |
108 | freeSharedLibEntry(pNewEntry); |
109 | return checkOnLoadResult(pActualEntry); |
112 | ALOGD( "Added shared lib %s %p" , pathName, classLoader); |
118 | vonLoad = dlsym(handle, "JNI_OnLoad" ); |
119 | if (vonLoad == NULL) { |
120 | ALOGD( "No JNI_OnLoad found in %s %p, skipping init" , |
121 | pathName, classLoader); |
129 | OnLoadFunc func = (OnLoadFunc)vonLoad; |
130 | Object* prevOverride = self->classLoaderOverride; |
132 | self->classLoaderOverride = classLoader; |
133 | oldStatus = dvmChangeStatus(self, THREAD_NATIVE); |
134 | if (gDvm.verboseJni) { |
135 | ALOGI( "[Calling JNI_OnLoad for \"%s\"]" , pathName); |
137 | version = (*func)(gDvmJni.jniVm, NULL); |
138 | dvmChangeStatus(self, oldStatus); |
139 | self->classLoaderOverride = prevOverride; |
141 | if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && |
142 | version != JNI_VERSION_1_6) |
144 | ALOGW( "JNI_OnLoad returned bad version (%d) in %s %p" , |
145 | version, pathName, classLoader); |
157 | if (gDvm.verboseJni) { |
158 | ALOGI( "[Returned from JNI_OnLoad for \"%s\"]" , pathName); |
164 | pNewEntry->onLoadResult = kOnLoadOkay; |
166 | pNewEntry->onLoadResult = kOnLoadFailed; |
168 | pNewEntry->onLoadThreadId = 0; |
173 | dvmLockMutex(&pNewEntry->onLoadLock); |
174 | pthread_cond_broadcast(&pNewEntry->onLoadCond); |
175 | dvmUnlockMutex(&pNewEntry->onLoadLock); |
哇塞,dvmLoadNativeCode()这个函数还真的是有点复杂,那就挑那些跟我们的JNI比较紧密相关的逻辑来看吧。可以认为这个函数做了下面的这样一些事情:
- 调用dlopen() 打开一个so文件,创建一个handle。
- 调用dlsym()函数,查找到so文件中的JNI_OnLoad()这个函数的函数指针。
- 执行上一步找到的那个JNI_OnLoad()函数。
至此,大体可以结束System.loadLibrary()的执行过程的分析。