System.loadLibrary源码分析

简介

现在越来越多的项目都会使用第三方so库,提交so库,那么自然想到的就是so库的导入,很多在使用so库的时候莫名会出现很多问题,不管是导库的过程,还是使用so库方法时。如果你对so使用一无所知,那么接下来的几篇文章将对你很有帮助,我会从浅到深将整个过程讲解清楚。

系统源码

Android8.0

System.loadLibrary

Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
  • 类加载器分类

    1. BootClassLoader,Android系统启动时加载系统相关的类文件。
    2. PathClassLoader,加载已经安装应用类,也可以加载/vendor/lib, /system/lib下的so库。
    3. 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方法

你可能感兴趣的:(System.loadLibrary源码分析)