Java動态庫加載
對于Android上層的Java代碼來說,隻需要一行代碼就即可完成so動态庫的加載過程:
System.load("/data/local/tmp/lib_test_jni.so");
System.loadLibrary("lib_test_jni");
以上兩個方法都用于加載動态庫,兩者的差別如下:
- 加載的路徑不同:System.load(String filename)是指定動态庫的完整路徑名;而System.loadLibrary(String libname)則隻會從指定lib目錄下查找,并加上lib字首和.so字尾;
- 自動加載庫的依賴庫的不同:System.load(String filename)不會自動加載依賴庫;而System.loadLibrary(String libname)會自動加載依賴庫。
動态庫加載原理分析
(1)System.loadLibrary
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
(2)Runtime.loadLibrary
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下來查找是否存在該動态庫;
(3)BaseDexClassLoader.findLibrary
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對象, 并且沒有覆寫該方法, 故調用其父類所對應的方法.
(4)DexPathList初始化
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初始化過程,主要功能是收集以下兩個變量資訊:
- dexElements: 記錄所有的dexFile檔案
- nativeLibraryPathElements: 記錄所有的Native動态庫, 包括app目錄的native庫和系統目錄的native庫.
(5)DexPathList.findLibrary
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
(6)System.mapLibraryName
public static String mapLibraryName(String nickname) {
if (nickname == null) {
throw new NullPointerException("nickname == null");
}
return "lib" + nickname + ".so";
}
該方法的功能是将xxx動态庫的名字轉換為libxxx.so
(7)Element.findNativeLibrary
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:
(8)Runtime.doLoad
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庫;
(9)nativeLoad
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()。
(10)LoadNativeLibrary
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;
}
該方法的主要工作過程:
- 檢查該動态庫是否已加載;
- 通過dlopen打開動态共享庫;
- 建立SharedLibrary共享庫,并添加到libraries_清單;
- 通過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()方法。