[Android]Java中System.loadLibrary() 的執行過程

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這個文件中):

01 /**
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.
05  *
06  * @param libName
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.
10  */
11 public static void loadLibrary(String libName) {
12     Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
13 }

由上面的那段code,可以看到,它的實現非常簡單,就只是先調用VMStack.getCallingClassLoader()獲取到ClassLoader,然後再把實際要做的事情委託給了Runtime來做而已。接下來我們再看一下Runtime.loadLibrary()的實現(在libcore/luni/src/main/java/java/lang/Runtime.java這個文件中):

01 /*
02  * Loads and links a library without security checks.
03  */
04 void loadLibrary(String libraryName, ClassLoader loader) {
05     if (loader != null) {
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");
11         }
12         String error = nativeLoad(filename, loader);
13         if (error != null) {
14             throw new UnsatisfiedLinkError(error);
15         }
16         return;
17     }
18  
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);
27             if (error == null) {
28                 return// We successfully loaded the library. Job done.
29             }
30             lastError = error;
31         }
32     }
33  
34     if (lastError != null) {
35         throw new UnsatisfiedLinkError(lastError);
36     }
37     throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
38 }

由上面的那段code,我們看到,loadLibrary()可以被看作是一個2步走的過程

  1. 獲取到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。
  2. 調用nativeLoad()這個native方法來load library

這段code,又牽出幾個問題,首先,可用的library path都是哪些,這實際上也決定了,我們的so文件放在哪些folder下,纔可以被真正load起來?其次,在native層load library的過程,又實際做了什麼事情?下面會對這兩個問題,一一的作出解答。

系統的library path

我們由簡單到複雜的來看這個問題。先來看一下,在傳入的ClassLoader爲空的情況(儘管我們知道,在System.loadLibrary()這個case下不會發生),前面Runtime.loadLibrary()的實現中那個mLibPaths的初始化的過程,在Runtime的構造函數中,如下:

01 /**
02  * Prevent this class from being instantiated.
03  */
04 private Runtime(){
05     String pathList = System.getProperty("java.library.path"".");
06     String pathSep = System.getProperty("path.separator"":");
07     String fileSep = System.getProperty("file.separator""/");
08  
09     mLibPaths = pathList.split(pathSep);
10  
11     // Add a '/' to the end so we don't have to do the property lookup
12     // and concatenation later.
13     for (int i = 0; i < mLibPaths.length; i++) {
14         if (!mLibPaths[i].endsWith(fileSep)) {
15             mLibPaths[i] += fileSep;
16         }
17     }
18 }

可以看到,那個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非空的情況,ClassLoaderfindLibrary()方法的執行過程。首先看一下它的實現(在libcore/luni/src/main/java/java/lang/ClassLoader.java這個文件中)

01 /**
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".
06  * <p>
07  * This implementation always returns {@code null}.
08  * </p>
09  *
10  * @param libName
11  *            the name of the library to find.
12  * @return the absolute path of the library.
13  */
14 protected String findLibrary(String libName) {
15     return null;
16 }

竟然是一個空函數。那系統中實際運行的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這個文件中 ):
1 @Override
2 public String findLibrary(String name) {
3     return pathList.findLibrary(name);
4 }

這個方法看上去倒挺簡單,不用多做解釋。然後來看那個pathList的初始化的過程,在BaseDexClassLoader的構造函數裏

01 /**
02  * Constructs an instance.
03  *
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
11  * {@code null}
12  * @param parent the parent class loader
13  */
14 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
15         String libraryPath, ClassLoader parent) {
16     super(parent);
17  
18     this.originalPath = dexPath;
19     this.originalLibraryPath = libraryPath;
20     this.pathList =
21         new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
22 }

BaseDexClassLoader的構造函數也不用多做解釋吧。然後是DexPathList的構造函數:

01 /**
02  * Constructs an instance.
03  *
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
13  */
14 public DexPathList(ClassLoader definingContext, String dexPath,
15         String libraryPath, File optimizedDirectory) {
16     if (definingContext == null) {
17         throw new NullPointerException("definingContext == null");
18     }
19  
20     if (dexPath == null) {
21         throw new NullPointerException("dexPath == null");
22     }
23  
24     if (optimizedDirectory != null) {
25         if (!optimizedDirectory.exists())  {
26             throw new IllegalArgumentException(
27                     "optimizedDirectory doesn't exist: "
28                     + optimizedDirectory);
29         }
30  
31         if (!(optimizedDirectory.canRead()
32                         && optimizedDirectory.canWrite())) {
33             throw new IllegalArgumentException(
34                     "optimizedDirectory not readable/writable: "
35                     + optimizedDirectory);
36         }
37     }
38  
39     this.definingContext = definingContext;
40     this.dexElements =
41         makeDexElements(splitDexPath(dexPath), optimizedDirectory);
42     this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
43 }

關於我們的library path的問題,可以只關注最後的那個splitLibraryPath(),這個地方,實際上即是把傳進來的libraryPath 又丟給splitLibraryPath來獲取library path 的list。可以看一下DexPathList.splitLibraryPath()的實現:

01 /**
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.
07  */
08 private static File[] splitLibraryPath(String path) {
09     /*
10      * Native libraries may exist in both the system and
11      * application library paths, and we use this search order:
12      *
13      *   1. this class loader's library path for application
14      *      libraries
15      *   2. the VM's library path from the system
16      *      property for system libraries
17      *
18      * This order was reversed prior to Gingerbread; see http://b/2933456.
19      */
20     ArrayList<File> result = splitPaths(
21             path, System.getProperty("java.library.path""."), true);
22     return result.toArray(new File[result.size()]);
23 }

這個地方,是在用兩個部分的library path list來由splitPaths構造最終的那個path list,一個部分是,傳進來的library path,另外一個部分是,像我們前面看到的那個,是system property。然後再來看一下DexPathList.splitPaths()的實現:

01 /**
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.
09  */
10 private static ArrayList<File> splitPaths(String path1, String path2,
11         boolean wantDirectories) {
12     ArrayList<File> result = new ArrayList<File>();
13  
14     splitAndAdd(path1, wantDirectories, result);
15     splitAndAdd(path2, wantDirectories, result);
16     return result;
17 }

總結一下,ClassLoader的那個findLibrary()實際上會在兩個部分的folder中去尋找System.loadLibrary()要load的那個library,一個部分是,構造ClassLoader時,傳進來的那個library path,即是app folder,另外一個部分是system property。在android系統中,查找要load的library,實際上會在如下3個folder中進行:

  1. /vendor/lib
  2. /system/lib
  3. /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這個文件中):

01 /*
02  * static String nativeLoad(String filename, ClassLoader loader)
03  *
04  * Load the specified full path as a dynamic library filled with
05  * JNI-compatible methods. Returns null on success, or a failure
06  * message on failure.
07  */
08 static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
09     JValue* pResult)
10 {
11     StringObject* fileNameObj = (StringObject*) args[0];
12     Object* classLoader = (Object*) args[1];
13     char* fileName = NULL;
14     StringObject* result = NULL;
15     char* reason = NULL;
16     bool success;
17  
18     assert(fileNameObj != NULL);
19     fileName = dvmCreateCstrFromString(fileNameObj);
20  
21     success = dvmLoadNativeCode(fileName, classLoader, &reason);
22     if (!success) {
23         const char* msg = (reason != NULL) ? reason : "unknown failure";
24         result = dvmCreateStringFromCstr(msg);
25         dvmReleaseTrackedAlloc((Object*) result, NULL);
26     }
27  
28     free(reason);
29     free(fileName);
30     RETURN_PTR(result);
31 }

可以看到,nativeLoad()實際上只是完成了兩件事情,第一,是調用dvmCreateCstrFromString()將Java 的library path String 轉換到native的String,然後將這個path傳給dvmLoadNativeCode()做load,dvmLoadNativeCode()這個函數的實現在dalvik/vm/Native.cpp中,如下:

001 /*
002  * Load native code from the specified absolute pathname.  Per the spec,
003  * if we've already loaded a library with the specified pathname, we
004  * return without doing anything.
005  *
006  * TODO? for better results we should absolutify the pathname.  For fully
007  * correct results we should stat to get the inode and compare that.  The
008  * existing implementation is fine so long as everybody is using
009  * System.loadLibrary.
010  *
011  * The library will be associated with the specified class loader.  The JNI
012  * spec says we can't load the same library into more than one class loader.
013  *
014  * Returns "true" on success. On failure, sets *detail to a
015  * human-readable description of the error or NULL if no detail is
016  * available; ownership of the string is transferred to the caller.
017  */
018 bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
019         char** detail)
020 {
021     SharedLib* pEntry;
022     void* handle;
023     bool verbose;
024  
025     /* reduce noise by not chattering about system libraries */
026     verbose = !!strncmp(pathName, "/system"sizeof("/system")-1);
027     verbose = verbose && !!strncmp(pathName, "/vendor"sizeof("/vendor")-1);
028  
029     if (verbose)
030         ALOGD("Trying to load lib %s %p", pathName, classLoader);
031  
032     *detail = NULL;
033  
034     /*
035      * See if we've already loaded it.  If we have, and the class loader
036      * matches, return successfully without doing anything.
037      */
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);
043             return false;
044         }
045         if (verbose) {
046             ALOGD("Shared lib '%s' already loaded in same CL %p",
047                 pathName, classLoader);
048         }
049         if (!checkOnLoadResult(pEntry))
050             return false;
051         return true;
052     }
053  
054     /*
055      * Open the shared library.  Because we're using a full path, the system
056      * doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
057      * resolve this library's dependencies though.)
058      *
059      * Failures here are expected when java.library.path has several entries
060      * and we have to hunt for the lib.
061      *
062      * The current version of the dynamic linker prints detailed information
063      * about dlopen() failures.  Some things to check if the message is
064      * cryptic:
065      *   - make sure the library exists on the device
066      *   - verify that the right path is being opened (the debug log message
067      *     above can help with that)
068      *   - check to see if the library is valid (e.g. not zero bytes long)
069      *   - check config/prelink-linux-arm.map to ensure that the library
070      *     is listed and is not being overrun by the previous entry (if
071      *     loading suddenly stops working on a prelinked library, this is
072      *     a good one to check)
073      *   - write a trivial app that calls sleep() then dlopen(), attach
074      *     to it with "strace -p <pid>" while it sleeps, and watch for
075      *     attempts to open nonexistent dependent shared libs
076      *
077      * This can execute slowly for a large library on a busy system, so we
078      * want to switch from RUNNING to VMWAIT while it executes.  This allows
079      * the GC to ignore us.
080      */
081     Thread* self = dvmThreadSelf();
082     ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
083     handle = dlopen(pathName, RTLD_LAZY);
084     dvmChangeStatus(self, oldStatus);
085  
086     if (handle == NULL) {
087         *detail = strdup(dlerror());
088         ALOGE("dlopen(\"%s\") failed: %s", pathName, *detail);
089         return false;
090     }
091  
092     /* create a new entry */
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;
101  
102     /* try to add it to the list */
103     SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
104  
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);
110     else {
111         if (verbose)
112             ALOGD("Added shared lib %s %p", pathName, classLoader);
113  
114         bool result = true;
115         void* vonLoad;
116         int version;
117  
118         vonLoad = dlsym(handle, "JNI_OnLoad");
119         if (vonLoad == NULL) {
120             ALOGD("No JNI_OnLoad found in %s %p, skipping init",
121                 pathName, classLoader);
122         else {
123             /*
124              * Call JNI_OnLoad.  We have to override the current class
125              * loader, which will always be "null" since the stuff at the
126              * top of the stack is around Runtime.loadLibrary().  (See
127              * the comments in the JNI FindClass function.)
128              */
129             OnLoadFunc func = (OnLoadFunc)vonLoad;
130             Object* prevOverride = self->classLoaderOverride;
131  
132             self->classLoaderOverride = classLoader;
133             oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
134             if (gDvm.verboseJni) {
135                 ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);
136             }
137             version = (*func)(gDvmJni.jniVm, NULL);
138             dvmChangeStatus(self, oldStatus);
139             self->classLoaderOverride = prevOverride;
140  
141             if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 &&
142                 version != JNI_VERSION_1_6)
143             {
144                 ALOGW("JNI_OnLoad returned bad version (%d) in %s %p",
145                     version, pathName, classLoader);
146                 /*
147                  * It's unwise to call dlclose() here, but we can mark it
148                  * as bad and ensure that future load attempts will fail.
149                  *
150                  * We don't know how far JNI_OnLoad got, so there could
151                  * be some partially-initialized stuff accessible through
152                  * newly-registered native method calls.  We could try to
153                  * unregister them, but that doesn't seem worthwhile.
154                  */
155                 result = false;
156             else {
157                 if (gDvm.verboseJni) {
158                     ALOGI("[Returned from JNI_OnLoad for \"%s\"]", pathName);
159                 }
160             }
161         }
162  
163         if (result)
164             pNewEntry->onLoadResult = kOnLoadOkay;
165         else
166             pNewEntry->onLoadResult = kOnLoadFailed;
167  
168         pNewEntry->onLoadThreadId = 0;
169  
170         /*
171          * Broadcast a wakeup to anybody sleeping on the condition variable.
172          */
173         dvmLockMutex(&pNewEntry->onLoadLock);
174         pthread_cond_broadcast(&pNewEntry->onLoadCond);
175         dvmUnlockMutex(&pNewEntry->onLoadLock);
176         return result;
177     }
178 }

哇塞,dvmLoadNativeCode()這個函數還真的是有點複雜,那就挑那些跟我們的JNI比較緊密相關的邏輯來看吧。可以認爲這個函數做了下面的這樣一些事情:

  1. 調用dlopen() 打開一個so文件,創建一個handle。
  2. 調用dlsym()函數,查找到so文件中的JNI_OnLoad()這個函數的函數指針。
  3. 執行上一步找到的那個JNI_OnLoad()函數。

至此,大體可以結束System.loadLibrary()的執行過程的分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章