Android JNI开辟:System.loadLibrary加载机制
源码流程分析在Android的JNI开辟中,加载.so文件通常使用System.loadLibrary函数。以下是一个简单的示例:
public class MainActivity extends AppCompatActivity {
// Used to load the 'myapplication' library on application startup.
static {
System.loadLibrary("myapplication");
}
}
接下来,我们以Android 10的源码为例,详细分析System.loadLibrary函数的实现过程。起首,System.loadLibrary的定义位于/libcore/ojluni/src/main/java/java/lang/System.java,实现如下:
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
从中可以看出,System.loadLibrary实际上是调用了Runtime类的loadLibrary0方法。我们继承跟踪loadLibrary0的实现:
void loadLibrary0(Class<?> fromClass, String libname) {
ClassLoader classLoader = ClassLoader.getClassLoader(fromClass);
loadLibrary0(classLoader, fromClass, libname);
}
在这里,通过传入的类获取了对应的类加载器(ClassLoader),然后调用了重载的loadLibrary0方法:
private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
// Check if the library name contains a directory separator
if (libname.indexOf((int) File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
// If loader is not null and not BootClassLoader, use it to find the library
if (loader != null && !(loader instanceof BootClassLoader)) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
String error = nativeLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
// If loader is null or is BootClassLoader, initialize library paths and load the library
getLibPaths();
String filename = System.mapLibraryName(libraryName);
String error = nativeLoad(filename, loader, callerClass);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}
这里loadLibrary0有一句关键代码:
oader.findLibrary:
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
实现如下:
public String findLibrary(String libraryName) {
// 最后会调用到C层中JNIEXPORT jstring JNICALL System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
// 结果就是会返回libmyapplication.so,拼接字符串
String fileName = System.mapLibraryName(libraryName);
// 返回libmyapplication.so的全路径
for (NativeLibraryElement element : nativeLibraryPathElements) {
String path = element.findNativeLibrary(fileName);
if (path != null) {
return path;
}
}
return null;
}
可以看到,findLibrary最终会通过JNI调用System.mapLibraryName方法拼接出完备的.so库文件名,并在本地路径中查找该文件。
接着看loadLibrary0中nativeLoad实现:
private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);
发现此时已经在C层,实现如下:
JNIEXPORT jstring JNICALL Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader, jclass caller)
{
return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
}
看来核心代码在JVM_NativeLoad函数中,继承跟进:
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader,
jclass caller) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == nullptr) {
return nullptr;
}
std::string error_msg;
{
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
caller,
&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());
}
vm->LoadNativeLibrary函数相对复杂,我们主要查看在这个函数内部调用android::OpenNativeLibrary函数:
void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
jobject class_loader, const char* caller_location, jstring library_path,
bool* needs_native_bridge, char** error_msg) {
#if defined(__ANDROID__)
UNUSED(target_sdk_version);
if (class_loader == nullptr) {
*needs_native_bridge = false;
if (caller_location != nullptr) {
android_namespace_t* boot_namespace = FindExportedNamespace(caller_location);
if (boot_namespace != nullptr) {
const android_dlextinfo dlextinfo = {
.flags = ANDROID_DLEXT_USE_NAMESPACE,
.library_namespace = boot_namespace,
};
void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);
if (handle == nullptr) {
*error_msg = strdup(dlerror());
}
return handle;
}
}
void* handle = dlopen(path, RTLD_NOW);
if (handle == nullptr) {
*error_msg = strdup(dlerror());
}
return handle;
}
// Additional code (not provided) would go here
#endif
}
总算追踪到了我们的老朋侪android_dlopen_ext和dlopen,最后经过追踪会调用do_dlopen函数,这就到本日的核心:
void* do_dlopen(const char* name, int flags,const android_dlextinfo* extinfo,const void* caller_addr) {
...
ProtectedDataGuard guard;
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
loading_trace.End();
if (si != nullptr) {
void* handle = si->to_handle();
LD_LOG(kLogDlopen,
"... dlopen calling constructors: realpath=\"%s\", soname=\"%s\", handle=%p",
si->get_realpath(), si->get_soname(), handle);
si->call_constructors();
failure_guard.Disable();
LD_LOG(kLogDlopen,
"... dlopen successful: realpath=\"%s\", soname=\"%s\", handle=%p",
si->get_realpath(), si->get_soname(), handle);
return handle;
}
也就是在在so加载完成以后会调用si->call_constructors()函数:
void soinfo::call_constructors() {
// 主要看这样两行
call_function("DT_INIT", init_func_, get_realpath());
call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false, get_realpath());
}
这里也就阐明白so中的init和init_array的实行时机在do_dlopen内部。此外,我们还需要相识JNI_OnLoad的实行时机。回到vm->LoadNativeLibrary函数中:
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
jclass caller_class,
std::string* error_msg) {
void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
if (sym == nullptr) {
VLOG(jni) << "";
was_successful = true;
} else {
ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
self->SetClassLoaderOverride(class_loader);
VLOG(jni) << "";
using JNI_OnLoadFn = int(*)(JavaVM*, void*);
JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
int version = (*jni_on_load)(this, nullptr);
}
可以看到就是通过library->FindSymbol查找JNI_OnLoad符号,然后使用函数指针进行JNI_OnLoad的函数调用。
总结
System.loadLibrary的核心功能是获取.so库的全路径,加载该.so库文件,然后依次调用init、init_array和JNI_OnLoad函数。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]