[Android]Java中System.loadLibrary() 的执行过程
来源:互联网 发布:java 如何建立socket 编辑:程序博客网 时间:2024/06/06 20:18
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步走的过程:
- 获取到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的构造函数中,如下:
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
* <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());
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<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中进行:
- /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这个文件中):
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比较紧密相关的逻辑来看吧。可以认为这个函数做了下面的这样一些事情:
- 调用dlopen() 打开一个so文件,创建一个handle。
- 调用dlsym()函数,查找到so文件中的JNI_OnLoad()这个函数的函数指针。
- 执行上一步找到的那个JNI_OnLoad()函数。
至此,大体可以结束System.loadLibrary()的执行过程的分析。
- [Android]Java中System.loadLibrary() 的执行过程
- Android Java中System.loadLibrary() 的执行过程
- [Android]Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- Java中System.loadLibrary() 的执行过程
- System.load 与 System.loadLibrary 的区别 Java中System.loadLibrary() 的执行过程
- Java基础知识JNI 在Android中使用System.loadLibrary()
- Java基础知识JNI 在Android中使用System.loadLibrary()
- 贪吃蛇 Java版(基于GUI)
- 20140714 「初等数论 - 拓展欧几里得+同余模方程」POJ 2115 C Looooops
- Windows XP模式
- HDU 4300 Clairewd’s message
- 打印输入中单词长度的直方图(垂直方向的直方图)
- [Android]Java中System.loadLibrary() 的执行过程
- google map api v3 的marker使用label的方法(markerwithlabel的使用)
- 深入理解Java内存模型(六)——final
- linux mmap初解
- py 异常
- uiwebview如何修改网络html
- GTMBase64
- robotium(上)
- SZ斐波拉契数列