JNI全称Java Native Interface,相当于java语言和C/C++语言打交道的桥梁。主要是用来在java语言中加载lib库。然后就可以在java中使用native中定义的方法。
用法如下:
static {
System.loadLibrary("TestJni");
}
private static native String getResult();
本文主要来跟踪一下System.loadLibrary的调用流程,不对jni文件实现做探究。
以Android N代码为例,System.loadLibrary源码位于:
./libcore/ojluni/src/main/java/java/lang/System.java
/**
* Loads the system library specified by the libname
* argument. The manner in which a library name is mapped to the
* actual system library is system dependent.
*
* The call System.loadLibrary(name)
is effectively
* equivalent to the call
*
* Runtime.getRuntime().loadLibrary(name)
*
*
* @param libname the name of the library.
* @exception SecurityException if a security manager exists and its
* checkLink
method doesn't allow
* loading of the specified dynamic library
* @exception UnsatisfiedLinkError if the library does not exist.
* @exception NullPointerException if libname
is
* null
* @see java.lang.Runtime#loadLibrary(java.lang.String)
* @see java.lang.SecurityManager#checkLink(java.lang.String)
*/
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
可以看到然后又调用了Runtime.java的loadLibrary0方法:
./libcore/ojluni/src/main/java/java/lang/Runtime.java
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
// It's not necessarily true that the ClassLoader used
// System.mapLibraryName, but the default setup does, and it's
// misleading to say we didn't find "libMyLibrary.so" when we
// actually searched for "liblibMyLibrary.so.so".
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
String filename = System.mapLibraryName(libraryName);
List candidates = new ArrayList();
String lastError = null;
for (String directory : getLibPaths()) {
String candidate = directory + filename;
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
这个方法主要有两个作用:
1. 找到lib的全称
2. 调用doLoad加载lib库
首先会判断loader 不为空来执行上面两步,如果为空,则换个方法继续执行上面两步。
这个方法的第一个参数是ClassLoader,是获取系统的loader,一般情况下这个值不会为空,是通过ContextImpl.java中的getClassLoader来生成的,且实例化为PathClassLoader。PathClassLoader继承BaseDexClassLoader,BaseDexClassLoader继承ClassLoader。
所以loader.findLibrary(libraryName)最终是调用BaseDexClassLoader中的findLibrary。
代码位于:./libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
其中pathList是一个DexPathList对象,位于./libcore/dalvik/src/main/java/dalvik/system/DexPathList.java:
/**
* Finds the named native code library on any of the library
* directories pointed at by this instance. This will find the
* one in the earliest listed directory, ignoring any that are not
* readable regular files.
*
* @return the complete path to the library or {@code null} if no
* library was found
*/
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (Element element : nativeLibraryPathElements) {
String path = element.findNativeLibrary(fileName);
if (path != null) {
return path;
}
}
return null;
}
可以看到这里会先调用System.java中的mapLibraryName方法来对libraryName做处理,这是一个native方法,在System.c中实现:
./libcore/ojluni/src/main/native/System.c
JNIEXPORT jstring JNICALL
System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
{
int len;
int prefix_len = (int) strlen(JNI_LIB_PREFIX);
int suffix_len = (int) strlen(JNI_LIB_SUFFIX);
jchar chars[256];
if (libname == NULL) {
JNU_ThrowNullPointerException(env, 0);
return NULL;
}
len = (*env)->GetStringLength(env, libname);
if (len > 240) {
JNU_ThrowIllegalArgumentException(env, "name too long");
return NULL;
}
cpchars(chars, JNI_LIB_PREFIX, prefix_len);
(*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);
len += prefix_len;
cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len);
len += suffix_len;
return (*env)->NewString(env, chars, len);
}
这个方法主要目的是给传进来的name添加前缀lib和后缀.so,最开始我们传进来的name是TestJni,所以此处处理后返回的是libTestJni.so.
接下来使用for循环对nativeLibraryPathElements调用findNativeLibrary方法。我们先看一下nativeLibraryPathElements是在哪里赋值的。在DexPathList的构造函数中:
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
ArrayList suppressedExceptions = new ArrayList();
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
// Native libraries may exist in both the system and
// application library paths, and we use this search order:
//
// 1. This class loader's library path for application libraries (librarySearchPath):
// 1.1. Native library directories
// 1.2. Path to libraries in apk-files
// 2. The VM's library path from the system property for system libraries
// also known as java.library.path
//
// This order was reversed prior to Gingerbread; see http://b/2933456.
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
suppressedExceptions,
definingContext);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
上述第44~50行代码就是nativeLibraryPathElements赋值的地方。
此处nativeLibraryPathElements依赖allNativeLibraryDirectories并通过makePathElements方法生成。
allNativeLibraryDirectories是一个list,由nativeLibraryDirectories和systemNativeLibraryDirectories组成。
librarySearchPath是由LoadedApk.java中createOrUpdateClassLoaderLocked方法中赋值的:
./frameworks/base/core/java/android/app/LoadedApk.java
final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
然后在ApplicationLoaders中实例化PathClassLoader时传递过去。在一般的app中类似于如下目录:
/data/app/com.example.testjni-1/lib/arm64
systemNativeLibraryDirectories是从系统java属性java.library.path中获取。Java.library.path这个值是在System.c的System_specialProperties方法中调用系统内核方法android_get_LD_LIBRARY_PATH赋值的。
android_get_LD_LIBRARY_PATH最终是在系统内核源码./bionic/linker/linker.cpp中实现:
void do_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {
// Use basic string manipulation calls to avoid snprintf.
// snprintf indirectly calls pthread_getspecific to get the size of a buffer.
// When debug malloc is enabled, this call returns 0. This in turn causes
// snprintf to do nothing, which causes libraries to fail to load.
// See b/17302493 for further details.
// Once the above bug is fixed, this code can be modified to use
// snprintf again.
size_t required_len = 0;
for (size_t i = 0; g_default_ld_paths[i] != nullptr; ++i) {
required_len += strlen(g_default_ld_paths[i]) + 1;
}
if (buffer_size < required_len) {
__libc_fatal("android_get_LD_LIBRARY_PATH failed, buffer too small: "
"buffer len %zu, required len %zu", buffer_size, required_len);
}
char* end = buffer;
for (size_t i = 0; g_default_ld_paths[i] != nullptr; ++i) {
if (i > 0) *end++ = ':';
end = stpcpy(end, g_default_ld_paths[i]);
}
}
g_default_ld_paths在init_default_namespace的时候被赋值:
if (bname && (strcmp(bname, "linker_asan") == 0 || strcmp(bname, "linker_asan64") == 0)) {
g_default_ld_paths = kAsanDefaultLdPaths;
} else {
g_default_ld_paths = kDefaultLdPaths;
}
static const char* const kDefaultLdPaths[] = {
#if defined(__LP64__)
"/system/lib64",
"/vendor/lib64",
#else
"/system/lib",
"/vendor/lib",
#endif
nullptr
};
所以systemNativeLibraryDirectories得值为 /system/lib64:/vendor/lib64
再返回到DexPathList的findLibrary方法,查看for循环,调用Element的findNativeLibrary方法查找fileName是否存在且可读,返回第一个存在且可读的path。
总结一下loadLibrary0的第一步,主要是调用loader.findLibrary(libraryName)完成以下任务:
1) 调用System.mapLibraryName(libraryName)拼接处完整的lib名字
2) 在下述3个路径查找lib库是否存在并返回第一个可读的文件路径:
/data/app/com.example.testjni-1/lib/arm64
/vendor/lib64
/system/lib64
如果lib库的路径不为空,接下来就调用doLoad进行下载。我们看下doLoad方法:
private String doLoad(String name, ClassLoader loader) {
// Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,
// which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.
// The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load
// libraries with no dependencies just fine, but an app that has multiple libraries that
// depend on each other needed to load them in most-dependent-first order.
// We added API to Android's dynamic linker so we can update the library path used for
// the currently-running process. We pull the desired path out of the ClassLoader here
// and pass it to nativeLoad so that it can call the private dynamic linker API.
// We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the
// beginning because multiple apks can run in the same process and third party code can
// use its own BaseDexClassLoader.
// We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any
// dlopen(3) calls made from a .so's JNI_OnLoad to work too.
// So, find out what the native library search path is for the ClassLoader in question...
String librarySearchPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
librarySearchPath = dexClassLoader.getLdLibraryPath();
}
// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
// internal natives.
synchronized (this) {
return nativeLoad(name, loader, librarySearchPath);
}
}
此处主要是先获取librarySearchPath,然后调用nativeLoad加载。
nativeLoad实现在libcore/ojluni/src/main/native/Runtime.c中:
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader, jstring javaLibrarySearchPath)
{
return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
}
接着调用 ./art/runtime/openjdkjvm/OpenjdkJvm.cc中的JVM_NativeLoad:
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader,
jstring javaLibrarySearchPath) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == NULL) {
return NULL;
}
std::string error_msg;
{
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
javaLibrarySearchPath,
&error_msg);
if (success) {
return nullptr;
}
}
此处先获取当前运行的虚拟机,然后调用虚拟机的LoadNativeLibrary,代码位于:
./art/runtime/java_vm_ext.cc:
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
jstring library_path,
std::string* error_msg) {
error_msg->clear();
// See if we've already loaded this library. If we have, and the class loader
// matches, return successfully without doing anything.
// TODO: for better results we should canonicalize the pathname (or even compare
// inodes). This implementation is fine if everybody is using System.loadLibrary.
SharedLibrary* library;
Thread* self = Thread::Current();
{
// TODO: move the locking (and more of this logic) into Libraries.
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
}
void* class_loader_allocator = nullptr;
{
ScopedObjectAccess soa(env);
// As the incoming class loader is reachable/alive during the call of this function,
// it's okay to decode it without worrying about unexpectedly marking it alive.
mirror::ClassLoader* loader = soa.Decode(class_loader);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
if (class_linker->IsBootClassLoader(soa, loader)) {
loader = nullptr;
class_loader = nullptr;
}
class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader);
CHECK(class_loader_allocator != nullptr);
}
if (library != nullptr) {
// Use the allocator pointers for class loader equality to avoid unnecessary weak root decode.
if (library->GetClassLoaderAllocator() != class_loader_allocator) {
// The library will be associated with class_loader. The JNI
// spec says we can't load the same library into more than one
// class loader.
StringAppendF(error_msg, "Shared library \"%s\" already opened by "
"ClassLoader %p; can't open in ClassLoader %p",
path.c_str(), library->GetClassLoader(), class_loader);
LOG(WARNING) << error_msg;
return false;
}
VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
<< " ClassLoader " << class_loader << "]";
if (!library->CheckOnLoadResult()) {
StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt "
"to load \"%s\"", path.c_str());
return false;
}
return true;
}
// 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.
Locks::mutator_lock_->AssertNotHeld(self);
const char* path_str = path.empty() ? nullptr : path.c_str();
void* handle = android::OpenNativeLibrary(env,
runtime_->GetTargetSdkVersion(),
path_str,
class_loader,
library_path);
bool needs_native_bridge = false;
if (handle == nullptr) {
if (android::NativeBridgeIsSupported(path_str)) {
handle = android::NativeBridgeLoadLibrary(path_str, RTLD_NOW);
needs_native_bridge = true;
}
}
VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";
if (handle == nullptr) {
*error_msg = dlerror();
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 new_library(
new SharedLibrary(env, self, path, handle, 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;
if (needs_native_bridge) {
library->SetNeedsNativeBridge();
}
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 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(sym);
int version = (*jni_on_load)(this, nullptr);
if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
fault_manager.EnsureArtActionInFrontOfSignalChain();
}
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 (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;
}
此方法异常复杂,我们不做深入分析,该方法大致做以下几步:
1) 调用android::OpenNativeLibrary打开lib库
2) 调用library->FindSymbol("JNI_OnLoad", nullptr)找到lib中的JNI_OnLoad这个方法
3) 执行JNI_OnLoad方法。
至此,java层真正调到lib库中,接下来的一些方法就需要jni层自己实现了。