转自:http://my.oschina.net/wolfcs/blog/129696
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());
|
在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
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
* 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比较紧密相关的逻辑来看吧。可以认为这个函数做了下面的这样一些事情:
- 调用dlopen() 打开一个so文件,创建一个handle。
- 调用dlsym()函数,查找到so文件中的JNI_OnLoad()这个函数的函数指针。
- 执行上一步找到的那个JNI_OnLoad()函数。
至此,大体可以结束System.loadLibrary()的执行过程的分析。
相关推荐
3. **使用绝对路径**:如果在代码中使用`System.loadLibrary("tcnative-1")`,可以考虑改为使用库文件的绝对路径。 4. **配置应用服务器**:对于像Tomcat这样的应用服务器,可以在其配置文件(如`catalina....
测试过程简介 1.c测试库libtest.so 环境:ubuntu10.4下 语言:c 编译库名称为:libtest.so 涉及文件:so_test.h test_a.c test_b.c test_c.c 终端执行命令:$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o...
在Java中,通过JNI(Java Native Interface)技术,可以调用这些本地库来执行特定的系统级任务,比如与硬件设备交互。在这个场景下,`ZebraNativeUsbAdapter_64.dll`和`ZebraNativeUsbAdapter_32.dll`是64位和32位的...
当Java程序试图通过`System.loadLibrary()`或`Runtime.getRuntime().loadLibrary()`方法加载非Java代码的本地库时,JVM会沿着这个路径寻找对应的库文件。 错误描述中提到的"NULL"可能是博主在遇到问题时没有提供...
Java中的`java.lang.System`类是Java核心库中不可或缺的一部分,它是系统级操作的主要入口点。这个类提供了很多静态字段和方法,用于处理系统级别的任务,如输入输出流管理、系统属性获取、时间戳获取以及资源加载等...
在Java应用程序中使用`OpenCV`,需要将`opencv-342.jar`添加到项目的类路径,并通过`System.loadLibrary()`加载对应的`.so`文件。例如: ```java System.loadLibrary(Core.NATIVE_LIBRARY_NAME); ``` `Core.NATIVE...
System.loadLibrary(Core.NATIVE_LIBRARY_NAME); } public static void main(String[] args) { Mat image = Imgcodecs.imread("path_to_your_image.jpg"); if (image.empty()) { System.out.println("无法...
在/android6.0/libcore/luni/src/main/java/java/lang/System.java中,load和loadLibrary函数的实现: public static void load(String pathName) { Runtime.getRuntime().load(pathName, VMStack....
5. **加载动态链接库**:在Java程序中使用`System.loadLibrary()`或`Runtime.loadLibrary()`方法加载编译好的动态链接库。例如: ```java System.loadLibrary("MyLib"); ``` 或者直接指定动态链接库的路径: ...
1. 创建`System.loadLibrary()`调用:在Java中,我们使用`java.lang.System.loadLibrary()`方法来加载DLL。你需要提供DLL的名称(不包括.dll扩展名),例如`System.loadLibrary("test1");` 2. 编写JNI头文件:为了...
3. **编写代码**:在Java代码中,使用`System.loadLibrary("opencv_java411")`加载动态库,然后就可以使用OpenCV的Java API进行图像处理。 4. **测试与运行**:编写并运行你的Java程序,确保OpenCV库被正确加载,...
使用`javah`工具,将Java类编译成`.class`文件后,对`.class`文件执行`javah`命令,它会根据Java类中的`native`方法生成对应的C/C++头文件。例如,对于上述`TestDll`类,`javah TestDll`会生成一个`TestDll.h`的...
System.loadLibrary(Core.NATIVE_LIBRARY_NAME); } public static void main(String[] args) { // 读取图像 Mat src = Imgcodecs.imread("input.jpg"); if (src.empty()) { System.out.println("无法读取...
5. **加载库**:在Java中,`System.loadLibrary()`会加载前面创建的动态链接库,使得Java能够调用C++实现的本地方法。 6. **测试和调试**:在Java程序中调用本地方法,验证C++代码是否正确工作。可能需要对JNI接口...
- **在Java中加载本地库**:使用`System.loadLibrary()`加载生成的DLL文件。 ```java public class Main { public static void main(String[] args) { MyDLL myDLL = new MyDLL() {}; System.loadLibrary(...
从`System.loadLibrary`方法的调用开始,经过一系列步骤最终将本地库中的JNI方法注册到Dalvik虚拟机中,使得Java层可以调用这些方法。通过对这一过程的理解,开发者能够更好地掌握JNI技术的应用,提高Android应用的...
4. 编译C++代码为动态链接库,并在Java中加载该库,例如`System.loadLibrary("cppDll")`。 5. 在Java中调用本地方法,传入参数,由JNI函数在C++中处理并返回结果。 总之,这个项目结合了Java的高级编程特性与C++的...
确保库名与Java类中的`System.loadLibrary`调用一致。 7. **运行Java程序**:最后,在Java程序中实例化`JavaTest`类,并调用`sayHello()`方法。Java虚拟机会自动加载C++编译的动态链接库,并调用对应的C++实现。 `...
5. 可以使用`logSystem.zip`中的工具或代码记录执行过程和结果,以便于调试和分析。 注意,执行shell命令需要`android.permission.ACCESS_SUPERUSER`权限,而且需要注意安全问题,因为shell命令可能会对设备造成...