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這個檔案中):
?1 2 3 4 5 6 7 8 9 10 11 12 13 |
/**
* 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這個檔案中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/*
* 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,我們看到,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的建構函式中,如下:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/**
* 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這些內容出來,就像下面這樣:
?1 2 3 |
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這個檔案中):
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/**
* 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:
?1 2 |
ClassLoader classLoader = getClassLoader();
Log.v(TAG,
"classLoader = " + classLoader.toString());
|
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]
|
1 2 3 4 |
@Override
public
String findLibrary(String name) {
return
pathList.findLibrary(name);
}
|
這個方法看上去倒挺簡單,不用多做解釋。然後來看那個pathList的初始化的過程,在BaseDexClassLoader的建構函式裡:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/**
* 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的建構函式:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/**
* 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()的實現:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/**
* 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()的實現:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/**
* 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中進行:
- /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這個檔案中):
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/*
* 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中,如下:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
/*
* Load native code from the specified absolute pathname. Per the spec,
* if we've already loaded a library with the specified pathname, we
|