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) << "[No JNI_OnLoad found in \"" << path << "\"]";
    was_successful = true;
} else {
    ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
    self->SetClassLoaderOverride(class_loader);

    VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
    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函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值