西河刘卡车医 发表于 2024-8-30 19:32:15

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]
查看完整版本: Android JNI开辟:System.loadLibrary加载机制