Android 9.0 System.loadLibrary 的源码解析
本文主要讲解下Android 9.0 System.loadLibrary
的源码实现。
源码分析
libcore/ojluni/src/main/java/java/lang/System.java
public static void loadLibrary(String libname) { Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);}//将libname加上前缀和后缀,即`lib.so` public static native String mapLibraryName(String libname);
System 的 loadLibrary 最终调用的是 Runtime 中的 loadLibrary0 方法。 mapLibraryName 方法主要是给 libname 加上 “lib” 的前缀和 “.so” 的后缀。所以我们加载 so 库时,不用带 “lib” 和 “.so” 的字符串。
libcore/ojluni/src/main/native/System.c
static void cpchars(jchar *dst, char *src, int n){ int i; for (i = 0; i < n; i++) { dst[i] = src[i]; }}JNIEXPORT jstring JNICALLSystem_mapLibraryName(JNIEnv *env, jclass ign, jstring libname){ int len; int prefix_len = (int) strlen(JNI_LIB_PREFIX); //JNI_LIB_PREFIX="lib" int suffix_len = (int) strlen(JNI_LIB_SUFFIX); //JNI_LIB_SUFFIX=".so" 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); //chars="lib" (*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);//chars="lib" len += prefix_len; cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len); //chars="lib"".so" len += suffix_len; return (*env)->NewString(env, chars, len);}
libcore/ojluni/src/main/java/java/lang/Runtime.java
synchronized void loadLibrary0(ClassLoader loader, String libname) { if (libname.indexOf((int)File.separatorChar) != -1) { throw new UnsatisfiedLinkError("Directory separator should not appear in library name: " + libname); } String libraryName = libname; if (loader != null) { //loader 不为空就,调用的 findLibrary。 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) + "\""); } //filename = /data/app/-xyz==/lib/arm64/lib.so String error = nativeLoad(filename, loader); if (error != null) { throw new UnsatisfiedLinkError(error); } return; }//filename=lib.so String filename = System.mapLibraryName(libraryName); List<String> candidates = new ArrayList<String>(); String lastError = null; //getLibPaths()=/system/lib64/ for (String directory : getLibPaths()) { //candidate=/system/lib64/lib.so String candidate = directory + filename; candidates.add(candidate); //如果存在/system/lib64/lib.so就加载so if (IoUtils.canOpenReadOnly(candidate)) { 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); } //如果都没有找到so就抛出异常。 throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);}private volatile String[] mLibPaths = null;private String[] getLibPaths() { if (mLibPaths == null) { synchronized(this) { if (mLibPaths == null) { mLibPaths = initLibPaths(); } } } return mLibPaths;}private static String[] initLibPaths() { String javaLibraryPath = System.getProperty("java.library.path"); //java.library.path="/system/lib64" if (javaLibraryPath == null) { return EmptyArray.STRING; } String[] paths = javaLibraryPath.split(":"); // Add a '/' to the end of each directory so we don't have to do it every time. for (int i = 0; i < paths.length; ++i) { if (!paths[i].endsWith("/")) { paths[i] += "/"; } } return paths;}
loadLibrary0 在加载 so 时,先判断 loader 是否为 null 。如果不为 null,就通过 loader 去找到 so 的绝对路径,然后再加载。如果为 null,就从 /system/lib64/ 中去找是否存在要加载的 so 。如果都没有找到就抛出异常。下面分析下 loader.findLibrary 方法。loader 集成自 BaseDexClassLoader。所以它最终调用的 BaseDexClassLoader 的 findLibrary 方法。
libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted) { super(parent); this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); if (reporter != null) { reportClassLoaderChain(); }}@Overridepublic String findLibrary(String name) { return pathList.findLibrary(name);}
BaseDexClassLoader 的 findLibrary 方法最终由 DexPathList 的 findLibrary 实现。而 DexPathList 的 findLibrary 是在 nativeLibraryPathElements 中遍历查找 存在的 so。nativeLibraryPathElements 的值来自 librarySearchPath 和 System.getProperty(“java.library.path”) 。librarySearchPath 主要是 /data/app/
的路径。System.getProperty(“java.library.path”) 为 /system/lib64/
。所以 DexPathList 是在这几个目录下查找 so 是否存在。如果存在就返回其绝对路径即可。
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) { ... //librarySearchPath="/data/app/-xyz==/lib/arm64:/data/app/-xyz==/base.apk!/lib/arm64-v8a" this.nativeLibraryDirectories = splitPaths(librarySearchPath, false); this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); //"java.library.path"="/system/lib64" List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);...}private static List<File> splitPaths(String searchPath, boolean directoriesOnly) { List<File> result = new ArrayList<>(); if (searchPath != null) { for (String path : searchPath.split(File.pathSeparator)) { if (directoriesOnly) { try { StructStat sb = Libcore.os.stat(path); if (!S_ISDIR(sb.st_mode)) { continue; } } catch (ErrnoException ignored) { continue; } } result.add(new File(path)); } } return result;}private static NativeLibraryElement[] makePathElements(List<File> files) { NativeLibraryElement[] elements = new NativeLibraryElement[files.size()]; int elementsPos = 0; for (File file : files) { String path = file.getPath(); if (path.contains(zipSeparator)) { //zipSeparator="!/" String split[] = path.split(zipSeparator, 2); File zip = new File(split[0]); String dir = split[1]; elements[elementsPos++] = new NativeLibraryElement(zip, dir); } else if (file.isDirectory()) { // We support directories for looking up native libraries. elements[elementsPos++] = new NativeLibraryElement(file); } } if (elementsPos != elements.length) { elements = Arrays.copyOf(elements, elementsPos); } return elements;}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;}static class NativeLibraryElement {public NativeLibraryElement(File dir) { this.path = dir; this.zipDir = null; } public NativeLibraryElement(File zip, String zipDir) { this.path = zip; this.zipDir = zipDir; if (zipDir == null) { throw new IllegalArgumentException(); } }public synchronized void maybeInit() { if (initialized) { return; } if (zipDir == null) { initialized = true; return; } try { urlHandler = new ClassPathURLStreamHandler(path.getPath()); } catch (IOException ioe) { System.logE("Unable to open zip file: " + path, ioe); urlHandler = null; } initialized = true; }public String findNativeLibrary(String name) { maybeInit(); if (zipDir == null) { String entryPath = new File(path, name).getPath(); if (IoUtils.canOpenReadOnly(entryPath)) { return entryPath; } } else if (urlHandler != null) { String entryName = zipDir + '/' + name; if (urlHandler.isEntryStored(entryName)) { return path.getPath() + zipSeparator + entryName; } } return null; }}
找到 so 的绝对路径后就是加载 so 了。so 的加载是通过 nativeLoad 方法实现的。java 层的 nativeLoad 对应的就是 c 层的 Runtime_nativeLoad 方法。
libcore/ojluni/src/main/native/Runtime.c
#define NATIVE_METHOD(className, functionName, signature) \{ #functionName, signature, (void*)(className ## _ ## functionName) }JNIEXPORT jstring JNICALLRuntime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename, jobject javaLoader){ return JVM_NativeLoad(env, javaFilename, javaLoader);}static JNINativeMethod gMethods[] = { FAST_NATIVE_METHOD(Runtime, freeMemory, "()J"), FAST_NATIVE_METHOD(Runtime, totalMemory, "()J"), FAST_NATIVE_METHOD(Runtime, maxMemory, "()J"), NATIVE_METHOD(Runtime, gc, "()V"), NATIVE_METHOD(Runtime, nativeExit, "(I)V"), NATIVE_METHOD(Runtime, nativeLoad, "(Ljava/lang/String;Ljava/lang/ClassLoader;)" "Ljava/lang/String;"), //{"nativeLoad", "(Ljava/lang/String;Ljava/lang/ClassLoader;)" "Ljava/lang/String;", (void*)Runtime_nativeLoad}};void register_java_lang_Runtime(JNIEnv* env) { jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));}
上面代码可以看出 Runtime_nativeLoad 调用的是 JVM_NativeLoad 方法。而 JVM_NativeLoad 真正实现so 的加载是在 vm->LoadNativeLibrary 方法中。
art/openjdkjvm/OpenjdkJvm.cc
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env, jstring javaFilename, jobject javaLoader) { ScopedUtfChars filename(env, javaFilename); if (filename.c_str() == NULL) { return NULL; } std::string error_msg; { art::JavaVMExt* vm = art::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());}
art/runtime/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, std::string* error_msg) { ... // 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. // Below we dlopen but there is no paired dlclose, this would be necessary if we supported // class unloading. Libraries will only be unloaded when the reference count (incremented by // dlopen) becomes zero from dlclose. // Retrieve the library path from the classloader, if necessary. ScopedLocalRef<jstring> library_path(env, GetLibrarySearchPath(env, class_loader)); Locks::mutator_lock_->AssertNotHeld(self); const char* path_str = path.empty() ? nullptr : path.c_str(); bool needs_native_bridge = false; void* handle = android::OpenNativeLibrary(env, runtime_->GetTargetSdkVersion(), path_str, class_loader, library_path.get(), &needs_native_bridge, error_msg); VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]"; if (handle == nullptr) { VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg; return false; } if (env->ExceptionCheck() == JNI_TRUE) { LOG(ERROR) << "Unexpected exception:"; env->ExceptionDescribe(); env->ExceptionClear(); } // Create a new entry. // TODO: move the locking (and more of this logic) into Libraries. bool created_library = false; { // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering. std::unique_ptr<SharedLibrary> new_library( new SharedLibrary(env, self, path, handle, needs_native_bridge, class_loader, class_loader_allocator)); MutexLock mu(self, *Locks::jni_libraries_lock_); library = libraries_->Get(path); if (library == nullptr) { // We won race to get libraries_lock. library = new_library.release(); libraries_->Put(path, library); created_library = true; } } if (!created_library) { LOG(INFO) << "WOW: we lost a race to add shared library: " << "\"" << path << "\" ClassLoader=" << class_loader; return library->CheckOnLoadResult(); } VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]"; bool was_successful = false; 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<jobject> 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<JNI_OnLoadFn>(sym); 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()); 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); // 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. } else { was_successful = true; } VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure") << " from JNI_OnLoad in \"" << path << "\"]"; } library->SetResult(was_successful); return was_successful;}class SharedLibrary { void SetNeedsNativeBridge(bool needs) { needs_native_bridge_ = needs; } bool NeedsNativeBridge() const { return needs_native_bridge_; } // No mutator lock since dlsym may block for a while if another thread is doing dlopen. void* FindSymbol(const std::string& symbol_name, const char* shorty = nullptr) REQUIRES(!Locks::mutator_lock_) { return NeedsNativeBridge() ? FindSymbolWithNativeBridge(symbol_name.c_str(), shorty) : FindSymbolWithoutNativeBridge(symbol_name.c_str()); } // No mutator lock since dlsym may block for a while if another thread is doing dlopen. void* FindSymbolWithoutNativeBridge(const std::string& symbol_name) REQUIRES(!Locks::mutator_lock_) { CHECK(!NeedsNativeBridge()); return dlsym(handle_, symbol_name.c_str()); } void* FindSymbolWithNativeBridge(const std::string& symbol_name, const char* shorty) REQUIRES(!Locks::mutator_lock_) { CHECK(NeedsNativeBridge()); uint32_t len = 0; return android::NativeBridgeGetTrampoline(handle_, symbol_name.c_str(), shorty, len); }}
LoadNativeLibrary 最终会通过 android::OpenNativeLibrary 去加载 so 库。然后判断 JNI_OnLoad 方法是否存在。如果存在就调用其方法。这也就是为什么我们在做 jni 开发时,要实现 JNI_OnLoad 方法来做一些初始化的操作。下面列出 OpenNativeLibrary 的源码实现,不在深入分析。现在新版本的加入了名字空间的概念,通过它来实现系统的私有 so 库,不被第三方加载。加载so不像之前的是用dlopen去加载 so 了。
system/core/libnativeloader/native_loader.cpp
void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path, jobject class_loader, jstring library_path, bool* needs_native_bridge, std::string* error_msg) { #if defined(__ANDROID__) UNUSED(target_sdk_version); if (class_loader == nullptr) { *needs_native_bridge = false; return dlopen(path, RTLD_NOW); } std::lock_guard<std::mutex> guard(g_namespaces_mutex); NativeLoaderNamespace ns; if (!g_namespaces->FindNamespaceByClassLoader(env, class_loader, &ns)) { // This is the case where the classloader was not created by ApplicationLoaders // In this case we create an isolated not-shared namespace for it. if (!g_namespaces->Create(env, target_sdk_version, class_loader, false /* is_shared */, false /* is_for_vendor */, library_path, nullptr, &ns, error_msg)) { return nullptr; } } if (ns.is_android_namespace()) { android_dlextinfo extinfo; extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE; extinfo.library_namespace = ns.get_android_ns(); void* handle = android_dlopen_ext(path, RTLD_NOW, &extinfo); if (handle == nullptr) { *error_msg = dlerror(); } *needs_native_bridge = false; return handle; } else { void* handle = NativeBridgeLoadLibraryExt(path, RTLD_NOW, ns.get_native_bridge_ns()); if (handle == nullptr) { *error_msg = NativeBridgeGetError(); } *needs_native_bridge = true; return handle; }#else UNUSED(env, target_sdk_version, class_loader); // Do some best effort to emulate library-path support. It will not // work for dependencies. // // Note: null has a special meaning and must be preserved. std::string c_library_path; // Empty string by default. if (library_path != nullptr && path != nullptr && path[0] != '/') { ScopedUtfChars library_path_utf_chars(env, library_path); c_library_path = library_path_utf_chars.c_str(); } std::vector<std::string> library_paths = base::Split(c_library_path, ":"); for (const std::string& lib_path : library_paths) { *needs_native_bridge = false; const char* path_arg; std::string complete_path; if (path == nullptr) { // Preserve null. path_arg = nullptr; } else { complete_path = lib_path; if (!complete_path.empty()) { complete_path.append("/"); } complete_path.append(path); path_arg = complete_path.c_str(); } void* handle = dlopen(path_arg, RTLD_NOW); if (handle != nullptr) { return handle; } if (NativeBridgeIsSupported(path_arg)) { *needs_native_bridge = true; handle = NativeBridgeLoadLibrary(path_arg, RTLD_NOW); if (handle != nullptr) { return handle; } *error_msg = NativeBridgeGetError(); } else { *error_msg = dlerror(); } } return nullptr;#endif}
总结
System.loadLibrary
的源码分析基本完成了。现在我们来重新整理下它们的调用关系。
System.loadLibrary Runtime.loadLibrary0 Runtime_nativeLoad JVM_NativeLoad LoadNativeLibrary OpenNativeLibrary
Android 7.0 开始,禁止加载非NDK库,也就是说系统禁止了应用去链接系统的私有库。它通过名字空间的方式来实现其方法。所以就看到了,我们加载 so 的时候是用 OpenNativeLibrary 方法,而不是以往的 dlopen 方法。
更多相关文章
- Android TextUtils类常用方法
- Android简单实现启动画面的方法
- Android 判断Root的方法
- android 获取锁屏,解锁的方法
- android中的activity里获得context方法
- android paint设置字体 中文字体 楷体 和自动换行方法(zhuan)
- Android发送短信,并监听短信发送后是否发送成功的实现方法