算是处于好奇吧,我们可以看一下这个方法它的实现,即执行流程。(下面分析的那些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 not 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 new ArrayList |
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步走的过程:
这段code,又牵出几个问题,首先,可用的library path都是哪些,这实际上也决定了,我们的so文件放在哪些folder下,才可以被真正load起来?其次,在native层load library的过程,又实际做了什么事情?下面会对这两个问题,一一的作出解答。
我们由简单到复杂的来看这个问题。先来看一下,在传入的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非空的情况,ClassLoader的findLibrary()方法的执行过程。首先看一下它的实现(在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 |
* |
07 |
* This implementation always returns {@code null}. |
08 |
* |
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()); |
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 |
@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 |
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 |
11 |
boolean wantDirectories) { |
12 |
ArrayList new ArrayList |
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中进行:
上面第3个item只是一个例子,每一个app,它的那个app library path的最后一个部分都会是特定于那个app的。至于说,构造BaseDexClassLoader时的那个libraryPath 到底是怎么来的,那可能就会牵扯到android本身更复杂的一些过程了,在此不再做更详细的说明。
然后来看一下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. |