前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android so 加载原理分析

Android so 加载原理分析

作者头像
望天
发布2020-01-02 14:35:36
7.3K0
发布2020-01-02 14:35:36
举报
文章被收录于专栏:along的开发之旅along的开发之旅

1. loadLibrary动态库加载过程分析

http://gityuan.com/2017/03/26/load_library/

2. 深入理解 System.loadLibrary

https://pqpo.me/2017/05/31/system-loadlibrary/

查看Android源码:

https://cs.android.com/

防止原文丢失,备份,推荐上述网址观看:

# loadLibrary动态库加载过程分析

本文讲述的Android系统体系架构中的动态库加载过程,相关源码如下:

代码语言:javascript
复制
libcore/luni/src/main/java/java/lang/System.java
libcore/luni/src/main/java/java/lang/Runtime.java
libcore/luni/src/main/native/java_lang_System.cpp
bionic/linker/linker.cpp
art/runtime/native/java_lang_Runtime.cc
art/runtime/java_vm_ext.cc

一. 概述

1.1 C++动态库加载

所需要的头文件的#include, 最为核心的方法如下:

代码语言:javascript
复制
void *dlopen(const char * pathname,int mode);  //打开动态库  
void *dlsym(void *handle,const char *name);  //获取动态库对象地址  
char *dlerror(vid);   //错误检测  
int dlclose(void * handle); //关闭动态库  

对于动态库加载过程先通过dlopen()打开动态库文件,再通过dlsym()获取动态库对象地址,加载完成则需要dlclose()关闭动态库。

1.2 Java动态库加载

对于android上层的Java代码来说,将以上方法都做好封装,只需要一行代码就即可完成动态库的加载过程:

代码语言:javascript
复制
System.load("/data/local/tmp/libgityuan_jni.so");
System.loadLibrary("gityuan_jni");

以上两个方法都用于加载动态库,两者的区别如下:

  • 加载的路径不同:System.load(String filename)是指定动态库的完整路径名;而System.loadLibrary(String libname)则只会从指定lib目录下查找,并加上lib前缀和.so后缀;
  • 自动加载库的依赖库的不同:System.load(String filename)不会自动加载依赖库;而System.loadLibrary(String libname)会自动加载依赖库。

这两方法功能基本一致,接下来从源码视角来看看loadLibrary()的加载过程。

二. 动态库加载过程

2.1 System.loadLibrary

[-> System.java]

代码语言:javascript
复制
public static void loadLibrary(String libName) {
    Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}

此处getCallingClassLoader返回的是调用者所定义的ClassLoader.

2.2 Runtime.loadLibrary

[-> Runtime.java]

代码语言:javascript
复制
void loadLibrary(String libraryName, ClassLoader loader) {
    if (loader != null) {
        //根据动态库名查看相应动态库的文件路径[见小节2.3]
        String filename = loader.findLibrary(libraryName);
        if (filename == null) {
            throw new UnsatisfiedLinkError(...);
        }
        //成功执行完doLoad,则返回 [见小节2.4]
        String error = doLoad(filename, loader);
        if (error != null) {
            throw new UnsatisfiedLinkError(error);
        }
        return;
    }

    //当loader为空的情况下执行[见小节2.3.3]
    String filename = System.mapLibraryName(libraryName);
    List<String> candidates = new ArrayList<String>();
    String lastError = null;

    //此处的mLibPaths取值 [见小节3.1]
    for (String directory : mLibPaths) {
        String candidate = directory + filename;
        candidates.add(candidate);
        //判断目标动态库是否存在
        if (IoUtils.canOpenReadOnly(candidate)) {
            //成功执行完doLoad,则返回 [见小节2.4]
            String error = doLoad(candidate, loader);
            if (error == null) {
                return; //则返回
            }
        }
    }
    ...
    throw new UnsatisfiedLinkError(...);
}

该方法主要是找到目标库所在路径后调用doLoad来真正用于加载动态库,其中会根据loader是否为空中间过程略有不同,分两种情况:

  • 当loader不为空时, 则通过loader.findLibrary()查看目标库所在路径;
  • 当loader为空时, 则从默认目录mLibPaths下来查找是否存在该动态库;

2.3 BaseDexClassLoader.findLibrary

[-> BaseDexClassLoader.java]

代码语言:javascript
复制
public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
        super(parent);
        //dexPath一般是指apk所在路径【小节2.3.1】
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

    public String findLibrary(String name) {
        return pathList.findLibrary(name); //[见小节2.3.2]
    }
}

ClassLoader一般来说都是PathClassLoader,从该对象的findLibrary说起. 由于PathClassLoader继承于 BaseDexClassLoader对象, 并且没有覆写该方法, 故调用其父类所对应的方法.

2.3.1 DexPathList初始化

[-> DexPathList.java]

代码语言:javascript
复制
public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {
    ...
    this.definingContext = definingContext;

    ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
    //记录所有的dexFile文件
    this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);

    //app目录的native库
    this.nativeLibraryDirectories = splitPaths(libraryPath, false);
    //系统目录的native库
    this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
    List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
    allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
    //记录所有的Native动态库
    this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null,
                                                      suppressedExceptions);
    ...
}

DexPathList初始化过程,主要功能是收集以下两个变量信息:

  1. dexElements: 记录所有的dexFile文件
  2. nativeLibraryPathElements: 记录所有的Native动态库, 包括app目录的native库和系统目录的native库.

接下来便是从pathList中查询目标动态库.

2.3.2 DexPathList.findLibrary

[-> DexPathList.java]

代码语言:javascript
复制
public String findLibrary(String libraryName) {
    //[见小节2.3.3]
    String fileName = System.mapLibraryName(libraryName);
    for (Element element : nativeLibraryPathElements) {
        //[见小节2.3.4]
        String path = element.findNativeLibrary(fileName);
        if (path != null) {
            return path;
        }
    }
    return null;
}

从所有的动态库nativeLibraryPathElements(包含两个系统路径)查询是否存在匹配的。一般地,64位系统的nativeLibraryPathElements取值:

  • /data/app/[packagename]-1/lib/arm64
  • /vendor/lib64
  • /system/lib64

2.3.3 System.mapLibraryName

[-> System.java]

代码语言:javascript
复制
public static String mapLibraryName(String nickname) {
    if (nickname == null) {
        throw new NullPointerException("nickname == null");
    }
    return "lib" + nickname + ".so";
}

该方法的功能是将xxx动态库的名字转换为libxxx.so,比如前面传递过来的nickname为gityuan_jni, 经过该方法处理后返回的名字为libgityuan_jni.so.

2.3.4 Element.findNativeLibrary

[-> DexPathList.java ::Element]

代码语言:javascript
复制
final class DexPathList {
    static class Element {

        public String findNativeLibrary(String name) {
            maybeInit();
            if (isDirectory) {
                String path = new File(dir, name).getPath();
                if (IoUtils.canOpenReadOnly(path)) {
                    return path;
                }
            } else if (zipFile != null) {
                String entryName = new File(dir, name).getPath();
                if (isZipEntryExistsAndStored(zipFile, entryName)) {
                  return zip.getPath() + zipSeparator + entryName;
                }
            }
            return null;
        }
    }
}

遍历查询,一旦找到则返回所找到的目标动态库. 接下来, 再回到[小节2.2]来看看动态库的加载doLoad:

2.4 Runtime.doLoad

[-> Runtime.java]

代码语言:javascript
复制
private String doLoad(String name, ClassLoader loader) {
    String ldLibraryPath = null;
    String dexPath = null;
    if (loader == null) {
        ldLibraryPath = System.getProperty("java.library.path");
    } else if (loader instanceof BaseDexClassLoader) {
        BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
        ldLibraryPath = dexClassLoader.getLdLibraryPath();
    }
    synchronized (this) {
        //[见小节2.4.1]
        return nativeLoad(name, loader, ldLibraryPath);
    }
}

此处ldLibraryPath有两种情况:

  • 当loader为空,则ldLibraryPath为系统目录下的Native库;
  • 当lodder不为空,则ldLibraryPath为app目录下的native库;

2.4.1 nativeLoad

[-> java_lang_Runtime.cc]

代码语言:javascript
复制
static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader,
                                  jstring javaLdLibraryPathJstr) {
  ScopedUtfChars filename(env, javaFilename);
  if (filename.c_str() == nullptr) {
    return nullptr;
  }

  SetLdLibraryPath(env, javaLdLibraryPathJstr);

  std::string error_msg;
  {
    JavaVMExt* vm = Runtime::Current()->GetJavaVM();
    //[见小节2.5]
    bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, &error_msg);
    if (success) {
      return nullptr;
    }
  }

  env->ExceptionClear();
  return env->NewStringUTF(error_msg.c_str());
}

nativeLoad方法经过jni所对应的方法是Runtime_nativeLoad()。

2.5 LoadNativeLibrary

[-> java_vm_ext.cc]

代码语言:javascript
复制
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader,
                                  std::string* error_msg) {
  error_msg->clear();

  SharedLibrary* library;
  Thread* self = Thread::Current();
  {
    MutexLock mu(self, *Locks::jni_libraries_lock_);
    library = libraries_->Get(path); //检查该动态库是否已加载
  }
  if (library != nullptr) {
    if (env->IsSameObject(library->GetClassLoader(), class_loader) == JNI_FALSE) {
      //不能加载同一个采用多个不同的ClassLoader
      return false;
    }
    ...
    return true;
  }

  const char* path_str = path.empty() ? nullptr : path.c_str();
  //通过dlopen打开动态共享库.该库不会立刻被卸载直到引用技术为空.
  void* handle = dlopen(path_str, RTLD_NOW);
  bool needs_native_bridge = false;
  if (handle == nullptr) {
    if (android::NativeBridgeIsSupported(path_str)) {
      handle = android::NativeBridgeLoadLibrary(path_str, RTLD_NOW);
      needs_native_bridge = true;
    }
  }

  if (handle == nullptr) {
    *error_msg = dlerror(); //打开失败
    VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg;
    return false;
  }


  bool created_library = false;
  {
    std::unique_ptr<SharedLibrary> new_library(
        new SharedLibrary(env, self, path, handle, class_loader));
    MutexLock mu(self, *Locks::jni_libraries_lock_);
    library = libraries_->Get(path);
    if (library == nullptr) {
      library = new_library.release();
      //创建共享库,并添加到列表
      libraries_->Put(path, library);
      created_library = true;
    }
  }
  ...

  bool was_successful = false;
  void* sym;
  //查询JNI_OnLoad符号所对应的方法
  if (needs_native_bridge) {
    library->SetNeedsNativeBridge();
    sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr);
  } else {
    sym = dlsym(handle, "JNI_OnLoad");
  }

  if (sym == nullptr) {
    was_successful = true;
  } else {
    //需要先覆盖当前ClassLoader.
    ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
    self->SetClassLoaderOverride(class_loader);

    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
    // 真正调用JNI_OnLoad()方法的过程
    int version = (*jni_on_load)(this, nullptr);

    if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
      fault_manager.EnsureArtActionInFrontOfSignalChain();
    }
    //执行完成后, 需要恢复到原来的ClassLoader
    self->SetClassLoaderOverride(old_class_loader.get());
    ...
  }

  library->SetResult(was_successful);
  return was_successful;
}

该方法的主要工作过程:

  1. 检查该动态库是否已加载;
  2. 通过dlopen打开动态共享库;
  3. 创建SharedLibrary共享库,并添加到libraries_列表;
  4. 通过dlsym获取JNI_OnLoad符号所对应的方法, 并调用该方法.

三. mLibPaths初始化

再来看看小节2.2中的mLibPaths的初始化过程, 要从libcore/luni/src/main/java/java/lang/System.java类的静态代码块初始化开始说起. 对于类的静态代码块,编译过程会将所有的静态代码块和静态成员变量的赋值过程都收集整合到clinit方法, 即类的初始化方法.如下:

代码语言:javascript
复制
public final class System {
    static {
        ...
        //[见小节3.1]
        unchangeableSystemProperties = initUnchangeableSystemProperties();
        systemProperties = createSystemProperties();
        addLegacyLocaleSystemProperties();
    }
}

3.1 initUnchangeableSystemProperties

[-> System.java]

代码语言:javascript
复制
private static Properties initUnchangeableSystemProperties() {
    VMRuntime runtime = VMRuntime.getRuntime();
    Properties p = new Properties();
    ...
    p.put("java.boot.class.path", runtime.bootClassPath());
    p.put("java.class.path", runtime.classPath());
    // [见小节3.2]
    parsePropertyAssignments(p, specialProperties());
    ...
    return p;
}

这个过程会将大量的key-value对保存到Properties对象, 这种重点看specialProperties

3.2 parsePropertyAssignments

[-> System.java]

代码语言:javascript
复制
private static void parsePropertyAssignments(Properties p, String[] assignments) {
    for (String assignment : assignments) {
        int split = assignment.indexOf('=');
        String key = assignment.substring(0, split);
        String value = assignment.substring(split + 1);
        p.put(key, value);
    }
}

将assignments数据解析后保存到Properties对象,而此处的assignments来源于specialProperties()方法,如下所示。

3.2.1 specialProperties

[-> java_lang_System.cpp]

代码语言:javascript
复制
static jobjectArray System_specialProperties(JNIEnv* env, jclass) {
    std::vector<std::string> properties;
    char path[PATH_MAX];
    ...

    const char* library_path = getenv("LD_LIBRARY_PATH");
    if (library_path == NULL) {
        // [见小节3.3]
        android_get_LD_LIBRARY_PATH(path, sizeof(path));
        library_path = path;
    }
    properties.push_back(std::string("java.library.path=") + library_path);
    return toStringArray(env, properties);
}

环境变量LD_LIBRARY_PATH为空, 可通过adb shell env命令来查看环境变量的值. 接下来进入android_get_LD_LIBRARY_PATH方法, 该方法调用do_android_get_LD_LIBRARY_PATH,见下文:

3.3 do_android_get_LD_LIBRARY_PATH

[-> linker.cpp]

代码语言:javascript
复制
void do_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {
  //[见小节3.4]
  size_t required_len = strlen(kDefaultLdPaths[0]) + strlen(kDefaultLdPaths[1]) + 2;
  char* end = stpcpy(buffer, kDefaultLdPaths[0]);
  *end = ':';
  strcpy(end + 1, kDefaultLdPaths[1]);
}

可见赋值过程还得看kDefaultLdPaths数组.

3.4 kDefaultLdPaths

[-> linker.cpp]

代码语言:javascript
复制
static const char* const kDefaultLdPaths[] = {
#if defined(__LP64__)
  "/vendor/lib64",
  "/system/lib64",
#else
  "/vendor/lib",
  "/system/lib",
#endif
  nullptr
};

mLibPaths值可通过System.getProperty(“java.library.path”)来查询,对于linux 64位操作系统, mLibPaths取值分两种情况:

  • 对于64系统,则为/system/lib64和/vendor/lib64;
  • 对于32系统,则为/system/lib和/vendor/lib.

假设是64位系统, 则会去查找/system/lib64/libgityuan_jni.so或/vendor/lib64/libgityuan_jni.so库是否存在.另外,大多数的动态库都在/system/lib64或/system/lib目录下。

四、小结

动态库加载程,调用栈如下:

代码语言:javascript
复制
System.loadLibrary()
  Runtime.loadLibrary()
    Runtime.doLoad()
      Runtime_nativeLoad()
          LoadNativeLibrary()
              dlopen()
              dlsym()
              JNI_OnLoad()

System.loadLibrary()和System.load()都用于加载动态库,loadLibrary()可以方便自动加载依赖库,load()可以方便地指定具体路径的动态库。对于loadLibrary()会将将xxx动态库的名字转换为libxxx.so,再从/data/app/[packagename]-1/lib/arm64,/vendor/lib64,/system/lib64等路径中查询对应的动态库。无论哪种方式,最终都会调用到LoadNativeLibrary()方法,该方法主要操作:

  • 通过dlopen打开动态共享库;
  • 通过dlsym获取JNI_OnLoad符号所对应的方法;
  • 调用该加载库中的JNI_OnLoad()方法。

----------------------

# 深入理解 System.loadLibrary

本文主要讲述 Android 加载动态链接库的过程及其涉及的底层原理。 会先以一个Linux的例子描述native层加载动态链接库的过程, 再从Java层由浅入深分析System.loadLibrary

首先我们知道在Android(Java)中加载一个动态链接库非常简单,只需一行代码:

代码语言:javascript
复制
System.loadLibrary("native-lib");

事实上这是Java提供的API,对于Java层实现基本一致,但是对于不同的JVM其底层(native)实现会有所差异。本文分析的代码基于Android 6.0系统。 看过《理解JNI技术》的应该知道上述代码执行过程中会调用native层的JNI_OnLoad方法,一般用于动态注册native方法。

# Linux 系统加载动态库过程分析

Android是基于Linux系统的,那么在Linux系统下是如何加载动态链接库的呢? 如果对此不敢兴趣或者对C++比较陌生的可以先跳到后面阅读Android Java层实现部分,但是最终还是会涉及到native代码。

Linux环境下加载动态库主要包括如下函数,位于头文件#include <dlfcn.h>中:

代码语言:javascript
复制
void *dlopen(const char *filename, int flag);  //打开动态链接库
char *dlerror(void);   //获取错误信息
void *dlsym(void *handle, const char *symbol);  //获取方法指针
int dlclose(void *handle); //关闭动态链接库  

可以通过下述命令可以查看上述函数的具体使用方法:

代码语言:javascript
复制
man dlopen

如何在Linux环境下生成动态链接库,如何加载并使用动态链接库中的函数?带着问题看个例子: 下面是一个简单的C++文件,作为动态链接库包含计算相关函数: [caculate.cpp]

代码语言:javascript
复制
extern "C"
int add(int a, int b) {
    return a + b;
}

extern "C"
int mul(int a, int b) {
    return a*b;
}

对于C++文件函数前的 extern “C” 不能省略,原因是C++编译之后会修改函数名,之后动态加载函数的时候会找不到该函数。加上extern “C”是告诉编译器以C的方式编译,不用修改函数名。 然后通过下述命令编译成动态链接库:

代码语言:javascript
复制
g++ -fPIC -shared caculate.cpp -o libcaculate.so

这样会在同级目录生成一个动态库文件:libcaculate.so 然后编写加载动态库并使用的代码: [main_call.cpp]

代码语言:javascript
复制
#include <iostream>
#include <dlfcn.h>

using namespace std;

static const char * const LIB_PATH = "./libcaculate.so";

typedef int (*CACULATE_FUNC)(int, int);

int main() {

	void* symAdd = nullptr;
	void* symMul = nullptr;
	char* errorMsg = nullptr;

	dlerror();
	//1.打开动态库,拿到一个动态库句柄
	void* handle = dlopen(LIB_PATH, RTLD_NOW);

	if(handle == nullptr) {
		cout << "load error!" << endl;
		return -1;
	}
        // 查看是否有错误
	if ((errorMsg = dlerror()) != nullptr) {
		cout << "errorMsg:" << errorMsg << endl;
		return -1;
	}
	
	cout << "load success!" << endl;
        
        //2.通过句柄和方法名获取方法指针地址
	symAdd = dlsym(handle, "add");
	if(symAdd == nullptr) {
		cout << "dlsym failed!" << endl;
		if ((errorMsg = dlerror()) != nullptr) {
		cout << "error message:" << errorMsg << endl;
		return -1;
	}
	}
        //3.将方法地址强制类型转换成方法指针
	CACULATE_FUNC addFunc = reinterpret_cast(symAdd);
        //4.调用动态库中的方法
	cout << "1 + 2 = " << addFunc(1, 2) << endl;
        //5.通过句柄关闭动态库
	dlclose(handle);
	return 0;
}

主要就用了上面提到的4个函数,过程如下:

  1. 打开动态库,拿到一个动态库句柄
  2. 通过句柄和方法名获取方法指针地址
  3. 将方法地址强制类型转换成方法指针
  4. 调用动态库中的方法
  5. 通过句柄关闭动态库

中间会使用dlerror检测是否有错误。 有必要解释一下的是方法指针地址到方法指针的转换,为了方便这里定义了一个方法指针的别名:

代码语言:javascript
复制
typedef int (*CACULATE_FUNC)(int, int);

指明该方法接受两个int类型参数返回一个int值。 拿到地址之后强制类型转换成方法指针用于调用:

代码语言:javascript
复制
CACULATE_FUNC addFunc = reinterpret_cast(symAdd);

最后只要编译运行即可:

代码语言:javascript
复制
g++ -std=c++11 -ldl main_call.cpp -o main
.main

因为代码中使用了c++11标准新加的特性,所以编译的时候带上-std=c++11,另外使用了头文件dlfcn.h需要带上-ldl,编译生成的main文件即是二进制可执行文件,需要将动态库放在同级目录下执行。 上面就是Linux环境下创建动态库,加载并使用动态库的全部过程。

由于Android基于Linux系统,所以我们有理由猜测Android系统底层也是通过这种方式加载并使用动态库的。下面开始从Android 上层 Java 代码开始分析。

# System.loadLibrary

[System.java]

代码语言:javascript
复制
public static void loadLibrary(String libName) {
    Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}

此处VMStack.getCallingClassLoader()拿到的是调用者的ClassLoader,一般情况下是PathClassLoader。 [Runtime.java]

代码语言:javascript
复制
    void loadLibrary(String libraryName, ClassLoader loader) {
        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 candidates = new ArrayList();
        String lastError = null;
        for (String directory : mLibPaths) {
            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);
    }

这里根据ClassLoader是否存在分了两种情况,当ClasssLoader存在的时候通过loader的findLibrary()查看目标库所在路径,当ClassLoader不存在的时候通过mLibPaths加载路径。最终都会调用doLoad加载动态库。 下面只讲ClassLoader存在的情况,不存在的情况更加简单。findLibrary位于PathClassLoader的父类BaseDexClassLoader中: [BaseDexClassLoader.java]

代码语言:javascript
复制
@Override
public String findLibrary(String name) {
   return pathList.findLibrary(name);
}

其中pathList的类型为DexPathList,它的构造方法如下: [DexPathList.java]

代码语言:javascript
复制
    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        // 省略其他代码
        this.nativeLibraryDirectories = splitPaths(libraryPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null,suppressedExceptions);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }

这里收集了apk的so目录,一般位于:/data/app/${package-name}/lib/arm/ 还有系统的so目录:System.getProperty(“java.library.path”),可以打印看一下它的值:/vendor/lib:/system/lib,其实就是前后两个目录,事实上64位系统是/vendor/lib64:/system/lib64。 最终查找so文件的时候就会在这三个路径中查找,优先查找apk目录。 [DexPathList.java]

代码语言:javascript
复制
 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;
    }

String fileName = System.mapLibraryName(libraryName)的实现很简单: [System.java]

代码语言:javascript
复制
public static String mapLibraryName(String nickname) {
        if (nickname == null) {
            throw new NullPointerException("nickname == null");
       }
        return "lib" + nickname + ".so";
}

也就是为什么动态库的命名必须以lib开头了。 然后会遍历nativeLibraryPathElements查找某个目录下是否有改文件,有的话就返回: [DexPathList.java]

代码语言:javascript
复制
public String findNativeLibrary(String name) {
      maybeInit();
      if (isDirectory) {
         String path = new File(dir, name).getPath();
         if (IoUtils.canOpenReadOnly(path)) {
             return path;
         }
      } else if (zipFile != null) {
         String entryName = new File(dir, name).getPath();
         if (isZipEntryExistsAndStored(zipFile, entryName)) {
             return zip.getPath() + zipSeparator + entryName;
         }
      }
      return null;
}

回到Runtime的loadLibrary方法,通过ClassLoader找到目标文件之后会调用doLoad方法: [Runtime.java]

代码语言:javascript
复制
private String doLoad(String name, ClassLoader loader) {
        String ldLibraryPath = null;
        String dexPath = null;
        if (loader == null) {
            ldLibraryPath = System.getProperty("java.library.path");
        } else if (loader instanceof BaseDexClassLoader) {
            BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
            ldLibraryPath = dexClassLoader.getLdLibraryPath();
        }
        synchronized (this) {
            return nativeLoad(name, loader, ldLibraryPath);
        }
    }

这里的ldLibraryPath和之前所述类似,loader为空时使用系统目录,否则使用ClassLoader提供的目录,ClassLoader提供的目录中包括apk目录和系统目录。 最后调用native代码: [java_lang_Runtime.cc]

代码语言:javascript
复制
static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader,jstring javaLdLibraryPathJstr) {
  ScopedUtfChars filename(env, javaFilename);
  if (filename.c_str() == nullptr) {
    return nullptr;
  }

  SetLdLibraryPath(env, javaLdLibraryPathJstr);

  std::string error_msg;
  {
    JavaVMExt* vm = 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());
}

继续调用JavaVMExt对象的LoadNativeLibrary方法: [java_vm_ext.cc]

代码语言:javascript
复制
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader,std::string* error_msg) {
  error_msg->clear();
  SharedLibrary* library;
  Thread* self = Thread::Current();
  {
    MutexLock mu(self, *Locks::jni_libraries_lock_);
    library = libraries_->Get(path);
  }
  if (library != nullptr) {
    if (env->IsSameObject(library->GetClassLoader(), class_loader) == JNI_FALSE) {
      StringAppendF(error_msg, "Shared library \"{936b63963a8c9f2b24063da536a495a32039ff9ed9d82cacc18dd4741407c351}s\" already opened by "
          "ClassLoader {936b63963a8c9f2b24063da536a495a32039ff9ed9d82cacc18dd4741407c351}p; can't open in ClassLoader {936b63963a8c9f2b24063da536a495a32039ff9ed9d82cacc18dd4741407c351}p",
          path.c_str(), library->GetClassLoader(), class_loader);
      LOG(WARNING) << error_msg;
      return false;
    }
    if (!library->CheckOnLoadResult()) {
      StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt "
          "to load \"{936b63963a8c9f2b24063da536a495a32039ff9ed9d82cacc18dd4741407c351}s\"", path.c_str());
      return false;
    }
    return true;
  }
  Locks::mutator_lock_->AssertNotHeld(self);
  const char* path_str = path.empty() ? nullptr : path.c_str();
  //1.打开动态链接库
  void* handle = dlopen(path_str, RTLD_NOW);
  bool needs_native_bridge = false;
  if (handle == nullptr) {
    if (android::NativeBridgeIsSupported(path_str)) {
      handle = android::NativeBridgeLoadLibrary(path_str, RTLD_NOW);
      needs_native_bridge = true;
    }
  }
  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();
  }
  bool created_library = false;
  {
    std::unique_ptr new_library(new SharedLibrary(env, self, path, handle, class_loader));
    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) {
     return library->CheckOnLoadResult();
  }
  bool was_successful = false; 
  void* sym; 
  if (needs_native_bridge) { library->SetNeedsNativeBridge();
    sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr);
  } else {
    //2.获取方法地址
    sym = dlsym(handle, "JNI_OnLoad");
  }
  if (sym == nullptr) {
    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
    was_successful = true;
  } else {
    ScopedLocalRef old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
    self->SetClassLoaderOverride(class_loader);
    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
    //3.强制类型转换成函数指针
    JNI_OnLoadFn jni_on_load = reinterpret_cast(sym);
    //4.调用函数
    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 \"{936b63963a8c9f2b24063da536a495a32039ff9ed9d82cacc18dd4741407c351}s\"", path.c_str());
    } else if (IsBadJniVersion(version)) {
      StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"{936b63963a8c9f2b24063da536a495a32039ff9ed9d82cacc18dd4741407c351}s\": {936b63963a8c9f2b24063da536a495a32039ff9ed9d82cacc18dd4741407c351}d",
                    path.c_str(), version);
    } else {
      was_successful = true;
    }
  return was_successful;
}

这个函数有点长,重点都用注释标注了。 开始的时候会去缓存查看是否已经加载过动态库,如果已经加载过会判断上次加载的ClassLoader和这次加载的ClassLoader是否一致,如果不一致则加载失败,如果一致则返回上次加载的结果,换句话说就是不允许不同的ClassLoader加载同一个动态库。为什么这么做值得推敲。 之后会通过dlopen打开动态共享库。然后会获取动态库中的JNI_OnLoad方法,如果有的话调用之。最后会通过JNI_OnLoad的返回值确定是否加载成功:

代码语言:javascript
复制
static bool IsBadJniVersion(int version) {
  // We don't support JNI_VERSION_1_1. These are the only other valid versions.
  return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6;
}

这也是为什么在JNI_OnLoad函数中必须正确返回的原因。 可以看到最终没有调用dlclose,当然也不能调用,这里只是加载,真正的函数调用还没有开始,之后就会使用dlopen拿到的句柄来访问动态库中的方法了。 看完这篇文章我们明确了几点:

  1. System.loadLibrary会优先查找apk中的so目录,再查找系统目录,系统目录包括:/vendor/lib(64),/system/lib(64)
  2. 不能使用不同的ClassLoader加载同一个动态库
  3. System.loadLibrary加载过程中会调用目标库的JNI_OnLoad方法,我们可以在动态库中加一个JNI_OnLoad方法用于动态注册
  4. 如果加了JNI_OnLoad方法,其的返回值为JNI_VERSION_1_2 ,JNI_VERSION_1_4, JNI_VERSION_1_6其一。我们一般使用JNI_VERSION_1_4即可
  5. Android动态库的加载与Linux一致使用dlopen系列函数,通过动态库的句柄和函数名称来调用动态库的函数
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # loadLibrary动态库加载过程分析
    • 一. 概述
      • 二. 动态库加载过程
        • 2.1 System.loadLibrary
        • 2.2 Runtime.loadLibrary
        • 2.3 BaseDexClassLoader.findLibrary
        • 2.4 Runtime.doLoad
        • 2.5 LoadNativeLibrary
      • 三. mLibPaths初始化
        • 3.1 initUnchangeableSystemProperties
        • 3.2 parsePropertyAssignments
        • 3.3 do_android_get_LD_LIBRARY_PATH
        • 3.4 kDefaultLdPaths
      • 四、小结
      • # 深入理解 System.loadLibrary
        • # Linux 系统加载动态库过程分析
          • # System.loadLibrary
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档