Android JNI之System.loadLibrary()流程
来源:互联网 发布:java遍历jsonobject 编辑:程序博客网 时间:2024/05/17 08:13
文档目的
JNI全称Java Native Interface,相当于java语言和C/C++语言打交道的桥梁。主要是用来在java语言中加载lib库。然后就可以在java中使用native中定义的方法。
用法如下:
- static {
- System.loadLibrary("TestJni");
- }
- private static native String getResult();
本文主要来跟踪一下System.loadLibrary的调用流程,不对jni文件实现做探究。
Library查找过程
以Android N代码为例,System.loadLibrary源码位于:
./libcore/ojluni/src/main/java/java/lang/System.java
- /**
- * Loads the system library specified by the <code>libname</code>
- * argument. The manner in which a library name is mapped to the
- * actual system library is system dependent.
- * <p>
- * The call <code>System.loadLibrary(name)</code> is effectively
- * equivalent to the call
- * <blockquote><pre>
- * Runtime.getRuntime().loadLibrary(name)
- * </pre></blockquote>
- *
- * @param libname the name of the library.
- * @exception SecurityException if a security manager exists and its
- * <code>checkLink</code> method doesn't allow
- * loading of the specified dynamic library
- * @exception UnsatisfiedLinkError if the library does not exist.
- * @exception NullPointerException if <code>libname</code> is
- * <code>null</code>
- * @see java.lang.Runtime#loadLibrary(java.lang.String)
- * @see java.lang.SecurityManager#checkLink(java.lang.String)
- */
- public static void loadLibrary(String libname) {
- Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
- }
可以看到然后又调用了Runtime.java的loadLibrary0方法:
./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) {
- 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 : getLibPaths()) {
- 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);
- }
这个方法主要有两个作用:
1. 找到lib的全称
2. 调用doLoad加载lib库
首先会判断loader 不为空来执行上面两步,如果为空,则换个方法继续执行上面两步。
这个方法的第一个参数是ClassLoader,是获取系统的loader,一般情况下这个值不会为空,是通过ContextImpl.java中的getClassLoader来生成的,且实例化为PathClassLoader。PathClassLoader继承BaseDexClassLoader,BaseDexClassLoader继承ClassLoader。
所以loader.findLibrary(libraryName)最终是调用BaseDexClassLoader中的findLibrary。
代码位于:./libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
- @Override
- public String findLibrary(String name) {
- return pathList.findLibrary(name);
- }
其中pathList是一个DexPathList对象,位于./libcore/dalvik/src/main/java/dalvik/system/DexPathList.java:
- /**
- * Finds the named native code library on any of the library
- * directories pointed at by this instance. This will find the
- * one in the earliest listed directory, ignoring any that are not
- * readable regular files.
- *
- * @return the complete path to the library or {@code null} if no
- * library was found
- */
- public String findLibrary(String libraryName) {
- String fileName = System.mapLibraryName(libraryName);
- for (Element element : nativeLibraryPathElements) {
- String path = element.findNativeLibrary(fileName);
- if (path != null) {
- return path;
- }
- }
- return null;
- }
可以看到这里会先调用System.java中的mapLibraryName方法来对libraryName做处理,这是一个native方法,在System.c中实现:
./libcore/ojluni/src/main/native/System.c
- JNIEXPORT jstring JNICALL
- 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);
- }
这个方法主要目的是给传进来的name添加前缀lib和后缀.so,最开始我们传进来的name是TestJni,所以此处处理后返回的是libTestJni.so.
接下来使用for循环对nativeLibraryPathElements调用findNativeLibrary方法。我们先看一下nativeLibraryPathElements是在哪里赋值的。在DexPathList的构造函数中:
- public DexPathList(ClassLoader definingContext, String dexPath,
- String librarySearchPath, 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;
- ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
- // save dexPath for BaseDexClassLoader
- this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
- suppressedExceptions, definingContext);
- // 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<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
- allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
- this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
- suppressedExceptions,
- definingContext);
- if (suppressedExceptions.size() > 0) {
- this.dexElementsSuppressedExceptions =
- suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
- } else {
- dexElementsSuppressedExceptions = null;
- }
- }
上述第44~50行代码就是nativeLibraryPathElements赋值的地方。
此处nativeLibraryPathElements依赖allNativeLibraryDirectories并通过makePathElements方法生成。
allNativeLibraryDirectories是一个list,由nativeLibraryDirectories和systemNativeLibraryDirectories组成。
librarySearchPath是由LoadedApk.java中createOrUpdateClassLoaderLocked方法中赋值的:
./frameworks/base/core/java/android/app/LoadedApk.java
- final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
然后在ApplicationLoaders中实例化PathClassLoader时传递过去。在一般的app中类似于如下目录:
- /data/app/com.example.testjni-1/lib/arm64
systemNativeLibraryDirectories是从系统java属性java.library.path中获取。Java.library.path这个值是在System.c的System_specialProperties方法中调用系统内核方法android_get_LD_LIBRARY_PATH赋值的。
android_get_LD_LIBRARY_PATH最终是在系统内核源码./bionic/linker/linker.cpp中实现:
- void do_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {
- // Use basic string manipulation calls to avoid snprintf.
- // snprintf indirectly calls pthread_getspecific to get the size of a buffer.
- // When debug malloc is enabled, this call returns 0. This in turn causes
- // snprintf to do nothing, which causes libraries to fail to load.
- // See b/17302493 for further details.
- // Once the above bug is fixed, this code can be modified to use
- // snprintf again.
- size_t required_len = 0;
- for (size_t i = 0; g_default_ld_paths[i] != nullptr; ++i) {
- required_len += strlen(g_default_ld_paths[i]) + 1;
- }
- if (buffer_size < required_len) {
- __libc_fatal("android_get_LD_LIBRARY_PATH failed, buffer too small: "
- "buffer len %zu, required len %zu", buffer_size, required_len);
- }
- char* end = buffer;
- for (size_t i = 0; g_default_ld_paths[i] != nullptr; ++i) {
- if (i > 0) *end++ = ':';
- end = stpcpy(end, g_default_ld_paths[i]);
- }
- }
g_default_ld_paths在init_default_namespace的时候被赋值:
- if (bname && (strcmp(bname, "linker_asan") == 0 || strcmp(bname, "linker_asan64") == 0)) {
- g_default_ld_paths = kAsanDefaultLdPaths;
- } else {
- g_default_ld_paths = kDefaultLdPaths;
- }
- static const char* const kDefaultLdPaths[] = {
- #if defined(__LP64__)
- "/system/lib64",
- "/vendor/lib64",
- #else
- "/system/lib",
- "/vendor/lib",
- #endif
- nullptr
- };
所以systemNativeLibraryDirectories得值为 /system/lib64:/vendor/lib64
再返回到DexPathList的findLibrary方法,查看for循环,调用Element的findNativeLibrary方法查找fileName是否存在且可读,返回第一个存在且可读的path。
总结一下loadLibrary0的第一步,主要是调用loader.findLibrary(libraryName)完成以下任务:
1) 调用System.mapLibraryName(libraryName)拼接处完整的lib名字
2) 在下述3个路径查找lib库是否存在并返回第一个可读的文件路径:
/data/app/com.example.testjni-1/lib/arm64
/vendor/lib64
/system/lib64
Library加载过程
如果lib库的路径不为空,接下来就调用doLoad进行下载。我们看下doLoad方法:
- private String doLoad(String name, ClassLoader loader) {
- // Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,
- // which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.
- // The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load
- // libraries with no dependencies just fine, but an app that has multiple libraries that
- // depend on each other needed to load them in most-dependent-first order.
- // We added API to Android's dynamic linker so we can update the library path used for
- // the currently-running process. We pull the desired path out of the ClassLoader here
- // and pass it to nativeLoad so that it can call the private dynamic linker API.
- // We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the
- // beginning because multiple apks can run in the same process and third party code can
- // use its own BaseDexClassLoader.
- // We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any
- // dlopen(3) calls made from a .so's JNI_OnLoad to work too.
- // So, find out what the native library search path is for the ClassLoader in question...
- String librarySearchPath = null;
- if (loader != null && loader instanceof BaseDexClassLoader) {
- BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
- librarySearchPath = 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, librarySearchPath);
- }
- }
此处主要是先获取librarySearchPath,然后调用nativeLoad加载。
nativeLoad实现在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) {
- 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,
- javaLibrarySearchPath,
- &error_msg);
- if (success) {
- return nullptr;
- }
- }
此处先获取当前运行的虚拟机,然后调用虚拟机的LoadNativeLibrary,代码位于:
./art/runtime/java_vm_ext.cc:
- bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
- const std::string& path,
- jobject class_loader,
- jstring library_path,
- std::string* error_msg) {
- error_msg->clear();
- // See if we've already loaded this library. If we have, and the class loader
- // matches, return successfully without doing anything.
- // TODO: for better results we should canonicalize the pathname (or even compare
- // inodes). This implementation is fine if everybody is using System.loadLibrary.
- SharedLibrary* library;
- Thread* self = Thread::Current();
- {
- // TODO: move the locking (and more of this logic) into Libraries.
- MutexLock mu(self, *Locks::jni_libraries_lock_);
- library = libraries_->Get(path);
- }
- void* class_loader_allocator = nullptr;
- {
- ScopedObjectAccess soa(env);
- // As the incoming class loader is reachable/alive during the call of this function,
- // it's okay to decode it without worrying about unexpectedly marking it alive.
- mirror::ClassLoader* loader = soa.Decode<mirror::ClassLoader*>(class_loader);
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- if (class_linker->IsBootClassLoader(soa, loader)) {
- loader = nullptr;
- class_loader = nullptr;
- }
- class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader);
- CHECK(class_loader_allocator != nullptr);
- }
- if (library != nullptr) {
- // Use the allocator pointers for class loader equality to avoid unnecessary weak root decode.
- if (library->GetClassLoaderAllocator() != class_loader_allocator) {
- // The library will be associated with class_loader. The JNI
- // spec says we can't load the same library into more than one
- // class loader.
- StringAppendF(error_msg, "Shared library \"%s\" already opened by "
- "ClassLoader %p; can't open in ClassLoader %p",
- path.c_str(), library->GetClassLoader(), class_loader);
- LOG(WARNING) << error_msg;
- return false;
- }
- VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
- << " ClassLoader " << class_loader << "]";
- if (!library->CheckOnLoadResult()) {
- StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt "
- "to load \"%s\"", path.c_str());
- 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.
- // 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.
- Locks::mutator_lock_->AssertNotHeld(self);
- const char* path_str = path.empty() ? nullptr : path.c_str();
- void* handle = android::OpenNativeLibrary(env,
- runtime_->GetTargetSdkVersion(),
- path_str,
- class_loader,
- library_path);
- bool needs_native_bridge = false;
- if (handle == nullptr) {
- if (android::NativeBridgeIsSupported(path_str)) {
- handle = android::NativeBridgeLoadLibrary(path_str, RTLD_NOW);
- needs_native_bridge = true;
- }
- }
- VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";
- if (handle == nullptr) {
- *error_msg = dlerror();
- 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, 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;
- if (needs_native_bridge) {
- library->SetNeedsNativeBridge();
- }
- 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) {
- fault_manager.EnsureArtActionInFrontOfSignalChain();
- }
- 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 (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;
- }
此方法异常复杂,我们不做深入分析,该方法大致做以下几步:
1) 调用android::OpenNativeLibrary打开lib库
2) 调用library->FindSymbol("JNI_OnLoad", nullptr)找到lib中的JNI_OnLoad这个方法
3) 执行JNI_OnLoad方法。
至此,java层真正调到lib库中,接下来的一些方法就需要jni层自己实现了。
- Android JNI之System.loadLibrary()流程
- Android JNI之System.loadLibrary()流程
- JNI so库加载流程之System.loadLibrary流程分析
- Android源码分析实战之JNI so库加载System.loadLibrary流程分析
- JNI 在Android中使用System.loadLibrary()
- JNI签名及System.loadLibrary()-android
- System.loadLibrary()流程分析
- Java基础知识JNI 在Android中使用System.loadLibrary()
- Java基础知识JNI 在Android中使用System.loadLibrary()
- java jni System.loadLibrary 接口的理解
- System.load 和 System.loadLibrary详解-JNI
- Android JNI学习之---JNI开发流程
- System.loadLibrary()是在使用Java的JNI机制时
- Android System.loadLibrary及JNI_OnLoad简介
- Android System.loadLibrary及JNI_OnLoad简介
- Dalvik模式下System.loadLibrary函数的执行流程分析
- [Android]Java中System.loadLibrary() 的执行过程
- Android Java中System.loadLibrary() 的执行过程
- JavaSwing学习总结
- pintos (1) -- Alarm Clock
- Golang 使用Mongodb之pipe
- Android Studio引用项目和引用jar包
- 事物的四个特性和四个隔离级别
- Android JNI之System.loadLibrary()流程
- 前辈指导的DWORD反转倒序
- 学习使用webpack+vue搭建项目
- 自定义函数结束后执行
- VLC-ANDROID开源项目的编译过程全纪录
- BZOJ1305 [CQOI2009]dance跳舞 【网络流】
- 【JAVA8】stream 流相关操作
- 图片转换为base64,经过post传输后‘+’会变成 ‘空格’
- maven热部署时遇到的问题