System.loadLibrary()是咱們在使用Java的JNI機制時,會用到的一個很是重要的函數,它的做用便是把實現了咱們在Java code中聲明的native方法的那個libraryload進來,或者load其餘什麼動態鏈接庫。java
算是處於好奇吧,咱們能夠看一下這個方法它的實現,即執行流程。(下面分析的那些code,來自於android 4.2.2 aosp版。)先看一下這個方法的code(在libcore/luni/src/main/java/java/lang/System.java這個文件中):node
/** * 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 no<span style="color:#003399;"></span>t be loaded. */ public static void loadLibrary(String libName) { Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader()); }
由上面的那段code,能夠看到,它的實現很是簡單,就只是先調用VMStack.getCallingClassLoader()獲取到ClassLoader,而後再把實際要作的事情委託給了Runtime來作而已。接下來咱們再看一下Runtime.loadLibrary()的實現(在libcore/luni/src/main/java/java/lang/Runtime.java這個文件中):linux
/* * 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 + " from loader " + loader + ": 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); }
這段code,又牽出幾個問題,首先,可用的library path都是哪些,這實際上也決定了,咱們的so文件放在哪些folder下,才能夠被真正load起來?其次,在native層load library的過程,又實際作了什麼事情?下面會對這兩個問題,一一的做出解答。android
咱們由簡單到複雜的來看這個問題。先來看一下,在傳入的ClassLoader爲空的狀況(儘管咱們知道,在System.loadLibrary()這個case下不會發生),前面Runtime.loadLibrary()的實現中那個mLibPaths的初始化的過程,在Runtime的構造函數中,以下:app
/** * Prevent this class from being instantiated. */ private Runtime(){ String pathList = System.getProperty("java.library.path", "."); String pathSep = System.getProperty("path.separator", ":"); String fileSep = System.getProperty("file.separator", "/"); mLibPaths = pathList.split(pathSep); // Add a '/' to the end so we don't have to do the property lookup // and concatenation later. for (int i = 0; i < mLibPaths.length; i++) { if (!mLibPaths[i].endsWith(fileSep)) { mLibPaths[i] += fileSep; } } }
|
能夠看到,那個library path list實際上讀取自一個system property。那在android系統中,這個system property的實際內容又是什麼呢?dump這些內容出來,就像下面這樣:ide
05-11 07:51:40.974: V/QRCodeActivity(11081): pathList = /vendor/lib:/system/lib 05-11 07:51:40.974: V/QRCodeActivity(11081): pathSep = : 05-11 07:51:40.974: V/QRCodeActivity(11081): fileSep = /
|
而後是傳入的ClassLoader非空的狀況,ClassLoader的findLibrary()方法的執行過程。首先看一下它的實現(在libcore/luni/src/main/java/java/lang/ClassLoader.java這個文件中):函數
/** * Returns the absolute path of the native library with the specified name, * or {@code null}. If this method returns {@code null} then the virtual * machine searches the directories specified by the system property * "java.library.path". * <p> * This implementation always returns {@code null}. * </p> * * @param libName * the name of the library to find. * @return the absolute path of the library. */ protected String findLibrary(String libName) { return null; } |
|
居然是一個空函數。那系統中實際運行的ClassLoader就是這個嗎?咱們能夠作一個小小的實驗,打印系統中實際運行的ClassLoader的String:this
|
ClassLoader classLoader = getClassLoader(); |
|
Log.v(TAG, "classLoader = " + classLoader.toString()); |
|
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] |
@Override public String findLibrary(String name) { return pathList.findLibrary(name); }
/** * 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.originalPath = dexPath; this.originalLibraryPath = libraryPath; this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); }
BaseDexClassLoader的構造函數也不用多作解釋吧。而後是DexPathList的構造函數:spa
/** * Constructs an instance. * * @param definingContext the context in which any as-yet unresolved * classes should be defined * @param dexPath list of dex/resource path elements, separated by * {@code File.pathSeparator} * @param libraryPath list of native library directory path elements, * separated by {@code File.pathSeparator} * @param optimizedDirectory directory where optimized {@code .dex} files * should be found and written to, or {@code null} to use the default * system directory for same */ public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { if (definingContext == null) { throw new NullPointerException("definingContext == null"); } if (dexPath == null) { throw new NullPointerException("dexPath == null"); } if (optimizedDirectory != null) { if (!optimizedDirectory.exists()) { throw new IllegalArgumentException( "optimizedDirectory doesn't exist: " + optimizedDirectory); } if (!(optimizedDirectory.canRead() && optimizedDirectory.canWrite())) { throw new IllegalArgumentException( "optimizedDirectory not readable/writable: " + optimizedDirectory); } } this.definingContext = definingContext; this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory); this.nativeLibraryDirectories = splitLibraryPath(libraryPath); }
關於咱們的library path的問題,能夠只關注最後的那個splitLibraryPath(),這個地方,實際上便是把傳進來的libraryPath 又丟給splitLibraryPath來獲取library path 的list。能夠看一下DexPathList.splitLibraryPath()的實現:debug
/** * Splits the given library directory path string into elements * using the path separator ({@code File.pathSeparator}, which * defaults to {@code ":"} on Android, appending on the elements * from the system library path, and pruning out any elements that * do not refer to existing and readable directories. */ private static File[] splitLibraryPath(String path) { /* * 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 * 2. the VM's library path from the system * property for system libraries * * This order was reversed prior to Gingerbread; see http://b/2933456. */ ArrayList<File> result = splitPaths( path, System.getProperty("java.library.path", "."), true); return result.toArray(new File[result.size()]); }
這個地方,是在用兩個部分的library path list來由splitPaths構造最終的那個path list,一個部分是,傳進來的library path,另一個部分是,像咱們前面看到的那個,是system property。而後再來看一下DexPathList.splitPaths()的實現:
/** * Splits the given path strings into file elements using the path * separator, combining the results and filtering out elements * that don't exist, aren't readable, or aren't either a regular * file or a directory (as specified). Either string may be empty * or {@code null}, in which case it is ignored. If both strings * are empty or {@code null}, or all elements get pruned out, then * this returns a zero-element list. */ private static ArrayList<File> splitPaths(String path1, String path2, boolean wantDirectories) { ArrayList<File> result = new ArrayList<File>(); splitAndAdd(path1, wantDirectories, result); splitAndAdd(path2, wantDirectories, result); return result; }
總結一下,ClassLoader的那個findLibrary()實際上會在兩個部分的folder中去尋找System.loadLibrary()要load的那個library,一個部分是,構造ClassLoader時,傳進來的那個library path,便是app folder,另一個部分是system property。在android系統中,查找要load的library,實際上會在以下3個folder中進行:
上面第3個item只是一個例子,每個app,它的那個app library path的最後一個部分都會是特定於那個app的。至於說,構造BaseDexClassLoader時的那個libraryPath 究竟是怎麼來的,那可能就會牽扯到android自己更復雜的一些過程了,在此再也不作更詳細的說明。
而後來看一下native層,把so文件load起的過程,先來一下nativeLoad()這個函數的實現(在JellyBean/dalvik/vm/native/java_lang_Runtime.cpp這個文件中):
/* * static String nativeLoad(String filename, ClassLoader loader) * * Load the specified full path as a dynamic library filled with * JNI-compatible methods. Returns null on success, or a failure * message on failure. */ static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args, JValue* pResult) { StringObject* fileNameObj = (StringObject*) args[0]; Object* classLoader = (Object*) args[1]; char* fileName = NULL; StringObject* result = NULL; char* reason = NULL; bool success; assert(fileNameObj != NULL); fileName = dvmCreateCstrFromString(fileNameObj); success = dvmLoadNativeCode(fileName, classLoader, &reason); if (!success) { const char* msg = (reason != NULL) ? reason : "unknown failure"; result = dvmCreateStringFromCstr(msg); dvmReleaseTrackedAlloc((Object*) result, NULL); } free(reason); free(fileName); RETURN_PTR(result); }
能夠看到,nativeLoad()實際上只是完成了兩件事情,第一,是調用dvmCreateCstrFromString()將Java 的library path String 轉換到native的String,而後將這個path傳給dvmLoadNativeCode()作load,dvmLoadNativeCode()這個函數的實如今dalvik/vm/Native.cpp中,以下:
/* * Load native code from the specified absolute pathname. Per the spec, * if we've already loaded a library with the specified pathname, we * return without doing anything. * * TODO? for better results we should absolutify the pathname. For fully * correct results we should stat to get the inode and compare that. The * existing implementation is fine so long as everybody is using * System.loadLibrary. * * The library will be associated with the specified class loader. The JNI * spec says we can't load the same library into more than one class loader. * * Returns "true" on success. On failure, sets *detail to a * human-readable description of the error or NULL if no detail is * available; ownership of the string is transferred to the caller. */ bool dvmLoadNativeCode(const char* pathName, Object* classLoader, char** detail) { SharedLib* pEntry; void* handle; bool verbose; /* reduce noise by not chattering about system libraries */ verbose = !!strncmp(pathName, "/system", sizeof("/system")-1); verbose = verbose && !!strncmp(pathName, "/vendor", sizeof("/vendor")-1); if (verbose) ALOGD("Trying to load lib %s %p", pathName, classLoader); *detail = NULL; /* * See if we've already loaded it. If we have, and the class loader * matches, return successfully without doing anything. */ pEntry = findSharedLibEntry(pathName); if (pEntry != NULL) { if (pEntry->classLoader != classLoader) { ALOGW("Shared lib '%s' already opened by CL %p; can't open in %p", pathName, pEntry->classLoader, classLoader); return false; } if (verbose) { ALOGD("Shared lib '%s' already loaded in same CL %p", pathName, classLoader); } if (!checkOnLoadResult(pEntry)) return false; return true; } /* * Open the shared library. Because we're using a full path, the system * doesn't have to search through LD_LIBRARY_PATH. (It may do so to * resolve this library's dependencies though.) * * Failures here are expected when java.library.path has several entries * and we have to hunt for the lib. * * The current version of the dynamic linker prints detailed information * about dlopen() failures. Some things to check if the message is * cryptic: * - make sure the library exists on the device * - verify that the right path is being opened (the debug log message * above can help with that) * - check to see if the library is valid (e.g. not zero bytes long) * - check config/prelink-linux-arm.map to ensure that the library * is listed and is not being overrun by the previous entry (if * loading suddenly stops working on a prelinked library, this is * a good one to check) * - write a trivial app that calls sleep() then dlopen(), attach * to it with "strace -p <pid>" while it sleeps, and watch for * attempts to open nonexistent dependent shared libs * * This can execute slowly for a large library on a busy system, so we * want to switch from RUNNING to VMWAIT while it executes. This allows * the GC to ignore us. */ Thread* self = dvmThreadSelf(); ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); handle = dlopen(pathName, RTLD_LAZY); dvmChangeStatus(self, oldStatus); if (handle == NULL) { *detail = strdup(dlerror()); ALOGE("dlopen(\"%s\") failed: %s", pathName, *detail); return false; } /* create a new entry */ SharedLib* pNewEntry; pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib)); pNewEntry->pathName = strdup(pathName); pNewEntry->handle = handle; pNewEntry->classLoader = classLoader; dvmInitMutex(&pNewEntry->onLoadLock); pthread_cond_init(&pNewEntry->onLoadCond, NULL); pNewEntry->onLoadThreadId = self->threadId; /* try to add it to the list */ SharedLib* pActualEntry = addSharedLibEntry(pNewEntry); if (pNewEntry != pActualEntry) { ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)", pathName, classLoader); freeSharedLibEntry(pNewEntry); return checkOnLoadResult(pActualEntry); } else { if (verbose) ALOGD("Added shared lib %s %p", pathName, classLoader); bool result = true; void* vonLoad; int version; vonLoad = dlsym(handle, "JNI_OnLoad"); if (vonLoad == NULL) { ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader); } else { /* * Call JNI_OnLoad. We have to override the current class * loader, which will always be "null" since the stuff at the * top of the stack is around Runtime.loadLibrary(). (See * the comments in the JNI FindClass function.) */ OnLoadFunc func = (OnLoadFunc)vonLoad; Object* prevOverride = self->classLoaderOverride; self->classLoaderOverride = classLoader; oldStatus = dvmChangeStatus(self, THREAD_NATIVE); if (gDvm.verboseJni) { ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName); } version = (*func)(gDvmJni.jniVm, NULL); dvmChangeStatus(self, oldStatus); self->classLoaderOverride = prevOverride; if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6) { ALOGW("JNI_OnLoad returned bad version (%d) in %s %p", version, pathName, classLoader); /* * It's unwise to call dlclose() here, but we can mark it * as bad and ensure that future load attempts will fail. * * We don't know how far JNI_OnLoad got, so there could * be some partially-initialized stuff accessible through * newly-registered native method calls. We could try to * unregister them, but that doesn't seem worthwhile. */ result = false; } else { if (gDvm.verboseJni) { ALOGI("[Returned from JNI_OnLoad for \"%s\"]", pathName); } } } if (result) pNewEntry->onLoadResult = kOnLoadOkay; else pNewEntry->onLoadResult = kOnLoadFailed; pNewEntry->onLoadThreadId = 0; /* * Broadcast a wakeup to anybody sleeping on the condition variable. */ dvmLockMutex(&pNewEntry->onLoadLock); pthread_cond_broadcast(&pNewEntry->onLoadCond); dvmUnlockMutex(&pNewEntry->onLoadLock); return result; } }
哇塞,dvmLoadNativeCode()這個函數還真的是有點複雜,那就挑那些跟咱們的JNI比較緊密相關的邏輯來看吧。能夠認爲這個函數作了下面的這樣一些事情:
至此,大致能夠結束System.loadLibrary()的執行過程的分析。