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;
}