本文主要讲解下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/-xyz==/lib/arm64:/data/app/-xyz==/base.apk!/lib/arm64-v8a 的路径。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 方法。

更多相关文章

  1. Android TextUtils类常用方法
  2. Android简单实现启动画面的方法
  3. Android 判断Root的方法
  4. android 获取锁屏,解锁的方法
  5. android中的activity里获得context方法
  6. android paint设置字体 中文字体 楷体 和自动换行方法(zhuan)
  7. Android发送短信,并监听短信发送后是否发送成功的实现方法

随机推荐

  1. 2012-7-23 android shape
  2. Android(安卓)Layout XML属性
  3. 中国移动开发者社区专访:学习Android的两
  4. Android(安卓)Service生命周期及用法!
  5. Android(安卓)Bluetooth蓝牙开发:Bluetoot
  6. Android(安卓)4.1源代码今日将发布
  7. Android启动模式
  8. Android中Activity启动模式详解
  9. Android中使用WebView, WebChromeClient
  10. Android(安卓)编程 设计规范