Android libxxx.so动态库加载

1、System.loadLibrary(“xxx”)为什么加载的是libxxx.so

2、动态库加载过程

3、java调用native方法时,JVM怎么知道要调用哪个JNI层函数

本文的目的就是对上述问题作相对全面的讲解。

本例中我们申明了一个包含本地方法的类

package com.demo.test;
class HelloWorld {
	private native void print();
	public static void main(String[] args) {
		new HelloWorld().print();
	}
	
	static {
		System.loadLibrary("HelloWorld");
	}
}

在Java中声明本地方法必须有"native"标识符,native修饰的方法只作为声明存在,具体实现在JNI层(C/C++函数)。 

在调用本地方法前,必须先装载含有该方法的本地库,如HelloWorld.java中所示,置于static块中通过System.loadLibrary方法装载,在Java VM初始化一个类时,首先会执行static块代码,这保证了调用本地方法前,装载了本地库。这里注意下虽然我们指定的动态库名称是helloworld, 实际上对应的是libhellowworld.so文件,后面会讲到。

本文虽然居于Android 34源码,但各版本原理都差不多一样,请放心阅读。

getRuntime()获取一个Runtime单例,再获取调用类的class对象

我们知道android应用的类都是由PathClassLoader加载的,所以这里的classLoader一定是一个PathClassLoader。之前在【学习MultiDex库如何动态加载dex或apk】介绍过这个类,有兴趣的可以去看看。

接着调用重载方法loadLibrary0(ClassLoader , Class<?>, String)从classLoader查找并加载动态库,不存在则抛出异常 UnsatisfiedLinkError。

loader.findLibrary()中调用System.mapLibraryName方法给 libraryName 加上 “lib” 前缀和 “.so” 后缀。这就是我们加载 so 库时,不用带 “lib” 和 “.so” 字符串的原因。 

结合上面的源码看下面的类图和调用关系,最终是从nativeLibraryPathElements按顺序查找动态库的,可以说nativeLibraryPathElements就是app的动态库查找路径集

那nativeLibraryPathElements是怎么来的?

在PathClassLoader初始化时, 创建了DexPathList

DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
   ……
    this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
    this.systemNativeLibraryDirectories =
            splitPaths(System.getProperty("java.library.path"), true);
    this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
}

 入参librarySearchPath是一个用默认路径分隔符“:”分割的多个路径组成的字符串,具体是什么值可以去看LoadedApk.java#createOrUpdateClassLoaderLocked##makePaths方法,一般是apk自己的lib目录。

splitPaths方法的作用是用默认路径分隔符将路径分割成路径集合List<File>;
​​​​​​​makePathElements把应用自身和系统的默认路径合并后赋值给nativeLibraryPathElements;

看一下运行时的值比较直观:

 nativeLibraryPathElements包含了目录和zip, 按照顺序查找library的时候,如果zipDir==null是目录,检查path/libxxx.so文件是否能够只读方式打开,类似于检查文件是否存在一样,但不需要读权限,打开成功则说明所需要的so文件存在, 返回entryPath;如果是zip, 会使用urlHandler检查base.apk中是否存在文件条目lib/armeabi-v7a/libxxx.so,存在则返回so文件路径 ********base.apk!/lib/armeabi-v7a/libxxx.so,这里的!/就是zipSeparator

找到 so 的绝对路径后就是加载so了。so的加载是通过 nativeLoad 方法实现的。java 层的 nativeLoad 对应的就是 c 层的 Runtime_nativeLoad 方法,发生错误抛出UnsatisfiedLinkError异常。

String error = nativeLoad(filename, loader);

if (error != null) {

    throw new UnsatisfiedLinkError(error);

}

//libcore/ojluni/src/main/native/Runtime.c

JNIEXPORT jstring JNICALL

Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,

                   jobject javaLoader)

{

    return JVM_NativeLoad(env, javaFilename, javaLoader);

}

Runtime_nativeLoad 调用的是 JVM_NativeLoad 方法。而 JVM_NativeLoad 真正实现so 的加载是在 vm->LoadNativeLibrary 方法中。

//art/openjdkjvm/OpenjdkJvm.cc

//art/runtime/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  std::string* error_msg) {
  ...

  // Open the shared library.  Because we're using a full path, the system
  // doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
  // resolve this library's dependencies though.)

  // Failures here are expected when java.library.path has several entries
  // and we have to hunt for the lib.

  // Below we dlopen but there is no paired dlclose, this would be necessary if we supported
  // class unloading. Libraries will only be unloaded when the reference count (incremented by
  // dlopen) becomes zero from dlclose.

  // Retrieve the library path from the classloader, if necessary.
  ScopedLocalRef<jstring> library_path(env, GetLibrarySearchPath(env, class_loader));

  Locks::mutator_lock_->AssertNotHeld(self);
  const char* path_str = path.empty() ? nullptr : path.c_str();
  bool needs_native_bridge = false;
  void* handle = android::OpenNativeLibrary(env,
                                            runtime_->GetTargetSdkVersion(),
                                            path_str,
                                            class_loader,
                                            library_path.get(),
                                            &needs_native_bridge,
                                            error_msg);

  VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";

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

  if (env->ExceptionCheck() == JNI_TRUE) {
    LOG(ERROR) << "Unexpected exception:";
    env->ExceptionDescribe();
    env->ExceptionClear();
  }
  // Create a new entry.
  // TODO: move the locking (and more of this logic) into Libraries.
  bool created_library = false;
  {
    // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering.
    std::unique_ptr<SharedLibrary> new_library(
        new SharedLibrary(env,
                          self,
                          path,
                          handle,
                          needs_native_bridge,
                          class_loader,
                          class_loader_allocator));

    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) {
    LOG(INFO) << "WOW: we lost a race to add shared library: "
        << "\"" << path << "\" ClassLoader=" << class_loader;
    return library->CheckOnLoadResult();
  }
  VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";

  bool was_successful = false;
  void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
  if (sym == nullptr) {
    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
    was_successful = true;
  } else {
    // Call JNI_OnLoad.  We have to override the current class
    // loader, which will always be "null" since the stuff at the
    // top of the stack is around Runtime.loadLibrary().  (See
    // the comments in the JNI FindClass function.)
    ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
    self->SetClassLoaderOverride(class_loader);

    VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
    int version = (*jni_on_load)(this, nullptr);

    if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
      // Make sure that sigchain owns SIGSEGV.
      EnsureFrontOfChain(SIGSEGV);
    }

    self->SetClassLoaderOverride(old_class_loader.get());

    if (version == JNI_ERR) {
      StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
    } else if (JavaVMExt::IsBadJniVersion(version)) {
      StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
                    path.c_str(), version);
      // It's unwise to call dlclose() here, but we can mark it
      // as bad and ensure that future load attempts will fail.
      // We don't know how far JNI_OnLoad got, so there could
      // be some partially-initialized stuff accessible through
      // newly-registered native method calls.  We could try to
      // unregister them, but that doesn't seem worthwhile.
    } else {
      was_successful = true;
    }
    VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
              << " from JNI_OnLoad in \"" << path << "\"]";
  }

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


class SharedLibrary {
    void SetNeedsNativeBridge(bool needs) {
        needs_native_bridge_ = needs;
    }

    bool NeedsNativeBridge() const {
    return needs_native_bridge_;
    }

    // No mutator lock since dlsym may block for a while if another thread is doing dlopen.
    void* FindSymbol(const std::string& symbol_name, const char* shorty = nullptr)
      REQUIRES(!Locks::mutator_lock_) {
    return NeedsNativeBridge()
        ? FindSymbolWithNativeBridge(symbol_name.c_str(), shorty)
        : FindSymbolWithoutNativeBridge(symbol_name.c_str());
    }

    // No mutator lock since dlsym may block for a while if another thread is doing dlopen.
    void* FindSymbolWithoutNativeBridge(const std::string& symbol_name)
      REQUIRES(!Locks::mutator_lock_) {
    CHECK(!NeedsNativeBridge());

    return dlsym(handle_, symbol_name.c_str());
    }

    void* FindSymbolWithNativeBridge(const std::string& symbol_name, const char* shorty)
      REQUIRES(!Locks::mutator_lock_) {
    CHECK(NeedsNativeBridge());

    uint32_t len = 0;
    return android::NativeBridgeGetTrampoline(handle_, symbol_name.c_str(), shorty, len);
    }
}

LoadNativeLibrary 最终会通过 android::OpenNativeLibrary 去加载 so 库。然后检查是否存在 JNI_OnLoad 方法。如果存在就调用其方法,这也就是为什么我们在做 jni 开发时,要实现 JNI_OnLoad 方法来做一些初始化的操,比如注册native方法。下面列出 OpenNativeLibrary 的源码实现,不做深入分析,大致是使用dlopen加载so, 然后获取JNI_OnLoad方法用的dlsym(handle_, symbol_name.c_str())。

//system/core/libnativeloader/native_loader.cp
void* OpenNativeLibrary(JNIEnv* env,
                        int32_t target_sdk_version,
                        const char* path,
                        jobject class_loader,
                        jstring library_path,
                        bool* needs_native_bridge,
                        std::string* error_msg) {
                        
#if defined(__ANDROID__)
  UNUSED(target_sdk_version);
  if (class_loader == nullptr) {
    *needs_native_bridge = false;
    return dlopen(path, RTLD_NOW);
  }

  std::lock_guard<std::mutex> guard(g_namespaces_mutex);
  NativeLoaderNamespace ns;

  if (!g_namespaces->FindNamespaceByClassLoader(env, class_loader, &ns)) {
    // This is the case where the classloader was not created by ApplicationLoaders
    // In this case we create an isolated not-shared namespace for it.
    if (!g_namespaces->Create(env,
                              target_sdk_version,
                              class_loader,
                              false /* is_shared */,
                              false /* is_for_vendor */,
                              library_path,
                              nullptr,
                              &ns,
                              error_msg)) {
      return nullptr;
    }
  }

  if (ns.is_android_namespace()) {
    android_dlextinfo extinfo;
    extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
    extinfo.library_namespace = ns.get_android_ns();

    void* handle = android_dlopen_ext(path, RTLD_NOW, &extinfo);
    if (handle == nullptr) {
      *error_msg = dlerror();
    }
    *needs_native_bridge = false;
    return handle;
  } else {
    void* handle = NativeBridgeLoadLibraryExt(path, RTLD_NOW, ns.get_native_bridge_ns());
    if (handle == nullptr) {
      *error_msg = NativeBridgeGetError();
    }
    *needs_native_bridge = true;
    return handle;
  }
#else
  UNUSED(env, target_sdk_version, class_loader);

  // Do some best effort to emulate library-path support. It will not
  // work for dependencies.
  //
  // Note: null has a special meaning and must be preserved.
  std::string c_library_path;  // Empty string by default.
  if (library_path != nullptr && path != nullptr && path[0] != '/') {
    ScopedUtfChars library_path_utf_chars(env, library_path);
    c_library_path = library_path_utf_chars.c_str();
  }

  std::vector<std::string> library_paths = base::Split(c_library_path, ":");

  for (const std::string& lib_path : library_paths) {
    *needs_native_bridge = false;
    const char* path_arg;
    std::string complete_path;
    if (path == nullptr) {
      // Preserve null.
      path_arg = nullptr;
    } else {
      complete_path = lib_path;
      if (!complete_path.empty()) {
        complete_path.append("/");
      }
      complete_path.append(path);
      path_arg = complete_path.c_str();
    }
    void* handle = dlopen(path_arg, RTLD_NOW);
    if (handle != nullptr) {
      return handle;
    }
    if (NativeBridgeIsSupported(path_arg)) {
      *needs_native_bridge = true;
      handle = NativeBridgeLoadLibrary(path_arg, RTLD_NOW);
      if (handle != nullptr) {
        return handle;
      }
      *error_msg = NativeBridgeGetError();
    } else {
      *error_msg = dlerror();
    }
  }
  return nullptr;
#endif
}

System.loadLibrary加载流程基本上结束了。我们重新整理调用关系:

System.loadLibrary

    Runtime.loadLibrary0

        Runtime_nativeLoad

            JVM_NativeLoad

                LoadNativeLibrary

                    OpenNativeLibrary

现在还剩最后一个问题了,Java代码里调用了native print(), JVM是怎么知道要调用哪个JNI层函数的?

这里涉及到了JNI方法的静态/动态注册,在JNI层面注册了Java层的本地方法后, 当Java代码调用print()方法时,JVM就知道应该调用哪个C函数来执行这个本地代码。

静态注册

默认行为,也最简单的方式。Java native 方法与 C 函数的映射关系有一个固定的命名规则:

Java_包名_类名_方法名

在首次调用Java native方法时,JVM会去查找加载的动态库中是否存在这样命名的函数,然后静态注册并调用。

比如本例中native print方法默认静态注册的JNI方法就是Java_com_demo_test_HelloWorld_print,注意方法签名也要匹配。

动态注册

提前手动建立映射关系,相对于静态注册,优点是不再根据特定名称查找函数的实现,带来两个好处:

1. 没有了冗杂的函数名,适用于大型项目开发;

2. 由于不再根据native函数查找对应的JNI层函数,所以首次调用速度比静态注册快;

一种可行的方法是基于JNI重载JNI_OnLoad(),在其中对Java层native函数进行动态注册。

// 这里是一个示例函数,实际中你可以根据需要实现不同的函数
void hello_print(JNIEnv* env, jobject obj) {
    printf("Hello, World!\n");
}

// 注册的函数表,每个函数的签名都是(JNIEnv*, jobject)
static JNINativeMethod methods[] = {
    {"print", "()V", (void*)hello_print},
    // 可以添加更多的方法,格式相同
};


// 定义 JNI_OnLoad 函数
JNIEXPORT  jint JNICALL  JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
 
    // 获取 JNI 接口指针
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return JNI_ERR; // 获取 JNI 接口失败
    }
 
    jclass clazz = (*env)->FindClass(env, "com/example/test/HelloWorld");
     if (clazz == NULL) {
        return JNI_ERR; // 类没有找到
    }

   // 注册所有的方法
    if ((*env)->RegisterNatives(class, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
        return JNI_ERR; // 注册失败
    }

    // 成功注册后,可以释放 JNIEnv 和类引用
    (*env)->DeleteLocalRef(class);
 
    // 成功返回支持的 JNI 版本
    return JNI_VERSION_1_4;
}

  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值