java vm ext.cc_深入分析Android加载so文件源码

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 candidates = new ArrayList();

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()进行加载加载。

9a428a3c75bab3101bd6e68c5422c243.png

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文件的执行。

14a5d818590a41f3292947fee259b769.png

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值