Android系统中使用ndk进行编程,有很多的好处(Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成;代码的保护:由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大;可以方便地使用C/C++开源库;便于移植,用C/C++写的库可以方便在其他平台上再次使用;提供程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能)。
要使用ndk进行编程,在Java层就必须要对so进行加载。Java层加载so的函数有两个:
System.load(String pathName)
System.loadLibraray(String libName)
两个函数的区别就是load函数的参数是so文件的绝对地址。loadLibrary的参数是so的名称,这个so文件必须放在apk的lib目录下,而且so的名称必须去掉前面的lib和后边的“.so”。如下所示:
System.load("/data/local/tmp/libhello.so");
System.loadLibrary("hello");
System.java
load
和loadLibraray
函数在/android6.0/libcore/luni/src/main/java/java/lang/System.java
中:
public static void load(String pathName) {
Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
}
/**
* See {@link Runtime#loadLibrary}.
*/
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
Runtime.java
getRuntime()
函数用于获取Runtime
的一个实例。
public static Runtime getRuntime() {
return mRuntime;
}
loadLibrary():
public void loadLibrary(String nickname) {
loadLibrary(nickname, VMStack.getCallingClassLoader());
}
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
// It's not necessarily true that the ClassLoader used
// System.mapLibraryName, but the default setup does, and it's
// misleading to say we didn't find "libMyLibrary.so" when we
// actually searched for "liblibMyLibrary.so.so".
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
String error = doLoad(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 (IoUtils.canOpenReadOnly(candidate)) {
String error = doLoad(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);
}
loadLibrary()
函数主要进行了两步操作。
第一步:获取library的path:
根据ClassLoader
的不同,会有两种不同的处理方法。
如果ClassLoader
非空,会利用ClassLoader的findLibrary()方法获取library的path。
如果ClassLoader
为空,会通过传入的library name和System.mapLibraryName
获得真正的library name。例如传入的是hello
,得到的是libhello.so
,然后在mLibPaths查找`libhello.so’,最终确定library的path。
第二步:调用doLoad()方法。
第一步目前我不关心,不去深究。主要看doLoad的实现。
private String doLoad(String name, ClassLoader loader) {
String ldLibraryPath = null;
String dexPath = null;
if (loader == null) {
// We use the given library path for the boot class loader. This is the path
// also used in loadLibraryName if loader is null.
ldLibraryPath = System.getProperty("java.library.path");
} else if (loader instanceof BaseDexClassLoader) {
BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
ldLibraryPath = dexClassLoader.getLdLibraryPath();
}
// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
// internal natives.
synchronized (this) {
return nativeLoad(name, loader, ldLibraryPath);
}
}
获得libbrary的路径;
调用native函数nativeLoad()进行加载加载。
java_lang_Runtime.cc
文件位置:/android6.0.1_r66/art/runtime/native/java_lang_Runtime.cc
static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader,
jstring javaLdLibraryPathJstr) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == nullptr) {
return nullptr;
}
SetLdLibraryPath(env, javaLdLibraryPathJstr);
std::string error_msg;
{
JavaVMExt* vm = Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, &error_msg);
if (success) {
return nullptr;
}
}
// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}
nativeLoad()
主要做了两件事:
第一件事:利用SetLdLibraryPath()
将Java的library的path转换成native的。
第二件事情:调用LoadNativeLibrary进行加载。<关键>
java_vm_ext.cc
位置:/android6.0/art/runtime/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader,
std::string* error_msg) {
...
const char* path_str = path.empty() ? nullptr : path.c_str();
void* handle = dlopen(path_str, RTLD_NOW);
...
if (needs_native_bridge) {
library->SetNeedsNativeBridge();
sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr);
} else {
sym = dlsym(handle, "JNI_OnLoad");
}
if (sym == nullptr) {
VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
was_successful = true;
} else {
- 利用
dlopen()
打开so文件,得到函数的指针 - 利用
dlsym()
调用so文件中的JNI_OnLoad方法,开始so文件的执行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。