简介
现在越来越多的项目都会使用第三方so库,提交so库,那么自然想到的就是so库的导入,很多在使用so库的时候莫名会出现很多问题,不管是导库的过程,还是使用so库方法时。如果你对so使用一无所知,那么接下来的几篇文章将对你很有帮助,我会从浅到深将整个过程讲解清楚。
系统源码
Android8.0
System.loadLibrary
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
-
类加载器分类
- BootClassLoader,Android系统启动时加载系统相关的类文件。
- PathClassLoader,加载已经安装应用类,也可以加载/vendor/lib, /system/lib下的so库。
- DexClassLoader,加载dex文件。
-
VMStack.getCallingClassLoader()
这里返回的是应用的类加载器。我们在Activity里面打印:Log.e("loadLibrary"," class_loader = ${this.classLoader}" )
打印结果:
class_loader = dalvik.system.PathClassLoader[DexPathList[[zip file"/data/app/blog.pds.com.socket-koKxbY-cMvLU6BAkp-notg==/base.apk"], nativeLibraryDirectories=[/data/app/blog.pds.com.socket-koKxbY-cMvLU6BAkp-notg==/lib/arm64, /system/lib64, /vendor/lib64]]]
so库目录
nativeLibraryDirectories=[/data/app/blog.pds.com.socket-koKxbY-cMvLU6BAkp-notg==/lib/arm64, /system/lib64, /vendor/lib64]]]
这里可以看出VMStack.getCallingClassLoader()返回的是PathClassLoader加载器对象。
-
loadLibrary0方法
synchronized void loadLibrary0(ClassLoader loader, String libname) { ... String libraryName = libname; if (loader != null) { String filename = loader.findLibrary(libraryName); ... String error = nativeLoad(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 : getLibPaths()) { String candidate = directory + filename; candidates.add(candidate); if (IoUtils.canOpenReadOnly(candidate)) { String error = nativeLoad(candidate, loader); if (error == null) { return; // We successfully loaded the library. Job done. } lastError = error; } } ... } loader一般都不会null,所以这里只分析loader != null的情况。来看一下loader.findLibrary方法。定位到dalvik.system.BaseDexClassLoader类(请下载Android源码查看该类)。
@Override public String findLibrary(String name) { return pathList.findLibrary(name); }
我们来看一下pathList的值是什么,查看BaseDexClassLoader的toString方法。
@Override public String toString() { return getClass().getName() + "[" + pathList + "]"; }
可以看出pathList就是上面打印日志[]里面的内容。pathList是一个DexPathList对象。
看一下DexPathList中的findLibrary方法public String findLibrary(String libraryName) { String fileName = System.mapLibraryName(libraryName); for (NativeLibraryElement element : nativeLibraryPathElements) { String path = element.findNativeLibrary(fileName); if (path != null) { return path; } } return null; }
获取so库完整名字
String fileName = System.mapLibraryName(libraryName)
定位到/libcore/ojluni/src/main/native/System.c文件
System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname) { int len; int prefix_len = (int) strlen(JNI_LIB_PREFIX); int suffix_len = (int) strlen(JNI_LIB_SUFFIX); jchar chars[256]; if (libname == NULL) { JNU_ThrowNullPointerException(env, 0); return NULL; } len = (*env)->GetStringLength(env, libname); if (len > 240) { JNU_ThrowIllegalArgumentException(env, "name too long"); return NULL; } cpchars(chars, JNI_LIB_PREFIX, prefix_len); (*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len); len += prefix_len; cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len); len += suffix_len; return (*env)->NewString(env, chars, len); }
可以看出 库的名字不能超过240个字符。看一下常量字符定义
#define JNI_LIB_PREFIX "lib" #ifdef __APPLE__ #define JNI_LIB_SUFFIX ".dylib" #define VERSIONED_JNI_LIB_NAME(NAME, VERSION) JNI_LIB_PREFIX NAME "." VERSION JNI_LIB_SUFFIX #else #define JNI_LIB_SUFFIX ".so"
APPLE是苹果平台的全集,所以JNI_LIB_SUFFIX定位为.so。cpchars(chars, JNI_LIB_PREFIX, prefix_len)和cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len)分别给给我们传入的库名字加上前缀"lib"和后缀".so"形成了完整的so库名字。
看一下nativeLibraryPathElements变量初始化
// 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 (librarySearchPath): // 1.1. Native library directories // 1.2. Path to libraries in apk-files // 2. The VM's library path from the system property for system libraries // also known as java.library.path // // This order was reversed prior to Gingerbread; see http://b/2933456. this.nativeLibraryDirectories = splitPaths(librarySearchPath, false); this.systemNativeLibraryDirectories =splitPaths(System.getProperty("java.library.path"), true); List
allNativeLibraryDirectories = new ArrayList<(nativeLibraryDirectories); allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); 从上面代码可以看出nativeLibraryPathElements包含了应用程序库库路径和系统库路径。
打印一下系统库路径:Log.e("loadLibrary"," system lib path =${System.getProperty("java.library.path")}" )
打印结果:
system lib path = /system/lib64:/vendor/lib64
找so库路径就分析到这里,如果找不到或者文件不能打开,者会报异常。
-
nativeLoad(filename, loader);
加载so库,我们想要调用so库里的方法,那么必须先加载so库文件。
定位到"/libcore/ojluni/src/main/native/Runtime.c"文件.JNIEXPORT jstring JNICALL Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename, jobject javaLoader, jstring javaLibrarySearchPath) { return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath); }
定位到/art/runtime/openjdkjvm/OpenjdkJvm.cc文件,找到JVM_NativeLoad方法:
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env, jstring javaFilename, jobject javaLoader, jstring javaLibrarySearchPath) { ... std::string error_msg; { art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM(); // 加载so库 bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, javaLibrarySearchPath, &error_msg); if (success) { return nullptr; } } ... }
定位到/art/runtime/java_vm_ext.cc文件找到LoadNativeLibrary方法
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, jstring library_path, std::string* error_msg) { ... void* handle = android::OpenNativeLibrary(env, runtime_->GetTargetSdkVersion(), path_str, class_loader, library_path, &needs_native_bridge, error_msg); ... // 找到so库里面的JNI_OnLoad方法指针 void* sym = library->FindSymbol("JNI_OnLoad", nullptr); if (sym == nullptr) { VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]"; was_successful = true; } 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.) ScopedLocalRef
old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride())); self->SetClassLoaderOverride(class_loader); VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]"; typedef int (*JNI_OnLoadFn)(JavaVM*, void*); // 强转 JNI_OnLoadFn jni_on_load = reinterpret_cast (sym); // 调用so库里面的JNI_OnLoad方法,所以动态注册可以在这里面完成 int version = (*jni_on_load)(this, nullptr); if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) { // Make sure that sigchain owns SIGSEGV. EnsureFrontOfChain(SIGSEGV); } self->SetClassLoaderOverride(old_class_loader.get()); // 版本检测,一会在JNI_OnLoad里面进行动态注册的时候需要返回一个int数字,返回的依据就是根据这里来的 if (version == JNI_ERR) { StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str()); } else if (JavaVMExt::IsBadJniVersion(version)) { StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d", path.c_str(), version); } else { was_successful = true; } ... } }
结束
到此System.loadLibrary就分析到这里,想要了解JNI动态注册的可以直接看下一篇文章。动态注册JNI方法