系统加载lib的方法:
- 通过JNI_OnLoad,实现动态注册;
- 如果没有定义JNI_OnLoad,则dvm调用dvmResolveNativeMethod进行动态解析。其实就是保存JNI层函数的函数指针,调用方法时直接使用这个函数指针。
静态注册是Java的Native方法通过方法指针来与JNI进行关联,动态注册可以让Native方法知道它在JNI中对应的方法指针。
JNI中有一种结构用来记录Java的Native方法和JNI方法的关联关系,它就是JNINativeMethod
typedef struct {
const char* name; // Java方法的名字
const char* signature; //Java方法的签名信息
void* fnPtr; //JNI中对应的方法指针
} JNINativeMethod;
注册流程
- 从java代码
System.loadLibrary(libName);
开始(libcore\ojluni\src\main\java\java\lang
); - 根据
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
来到java/lang/Runtime.java
里,代码如下
// libcore\ojluni\src\main\java\java\lang\Runtime.java 注意这个Runtime是java核心库里面的和AndroidRuntime无关系
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;
...
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()) { / / getLibPaths()用来获取系统中存放so库的文件路径
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);
}
private String doLoad(String name, ClassLoader loader) {
if (loader != null && loader instanceof BaseDexClassLoader) {
BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
librarySearchPath = dexClassLoader.getLdLibraryPath();
}
synchronized (this) {
return nativeLoad(name, loader, librarySearchPath);// 调用本地方法nativeLoad
}
}
- 上面doLoad()->nativeLoad(); 本地方法对应的源文件为java_lang_Runtime.cc;
// libcore\ojluni\\src\main\native\Runtime.c 注意这个Runtime是java核心库里面的和AndroidRuntime无关系
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader, jstring javaLibrarySearchPath)
{
// //调用JVM_NativeLoad方法,该方法申明在jvm.h中,实现在OpenjdkJvm.cc中
return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
}
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(Runtime, freeMemory, "!()J"),
NATIVE_METHOD(Runtime, totalMemory, "!()J"),
NATIVE_METHOD(Runtime, maxMemory, "!()J"),
NATIVE_METHOD(Runtime, gc, "()V"),
NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
NATIVE_METHOD(Runtime, nativeLoad,
"(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;)"
"Ljava/lang/String;"),
};
void register_java_lang_Runtime(JNIEnv* env) {
jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));
}
- art/runtime/openjdkjvm/OpenjdkJvm.cc
// art/runtime/openjdkjvm/OpenjdkJvm.cc
#include "../../libcore/ojluni/src/main/native/jvm.h"
//JVM_NativeLoad方法的实现
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader,
jstring javaLibrarySearchPath) {
...
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
javaLibrarySearchPath,
&error_msg); // 调用JavaVMExt的LoadNativeLibrary方法
if (success) {
return nullptr;
}
...
return env->NewStringUTF(error_msg.c_str());
}
- 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) {
...
sym = library->FindSymbol("JNI_OnLoad", nullptr);
// 在我们要加载so库中查找JNI_OnLoad方法,如果没有就认为是静态注册方式,代表so库加载成功,如果找到JNI_OnLoad就会调用JNI_OnLoad方法,
// JNI_OnLoad方法中一般存放的是方法注册的函数,所以如果采用动态注册就必须要实现JNI_OnLoad方法
if (sym == nullptrr) {
was_successful = true;
} else {
typedef int (*JNI_OnLoadFn)(JavaVM*, void*);//定义了一个
JNI_OnLoadFn jni_on_load = reinterpret_cast(sym);
// sym是void*类型的,任何一个类型都可以用void*类型进行传递,但是void*类型是不能够调用的,
// 所用在调用之前,需要将void*代表的类型转换为其原来的类型,在这里,把sym重新解释为JNI_OnLoadFn,
// sym指向的是JNI_OnLoad,JNI_OnLoad和JNI_OnLoadFn是相同的类型,其实在我的理解中就是让JNI_OnLoadFn指向JNI_OnLoad函数的地址,
// 这样调用JNI_OnLoadFn就像调用JNI_OnLoad一样
int version = (*jni_on_load)(this, nullptr);//调用JNI_OnLoad函数,version为JNI_OnLoad函数的返回值
}
...
动态注册和静态注册代码示例
#include
#include
#include
#define TAG "test-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
/**
* 静态注册方式,方法名按照标准写法Java_包名_类名_方法名
*/
extern "C" JNIEXPORT jstring JNICALL Java_com_jianjin33_demo2_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */)
{
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
// 接下来是动态注册的方式,需要实现jni.h中的JNI_OnLoad方法
// 方法名可以随意些
extern "C" JNIEXPORT jstring JNICALL native_stringFromJni2(JNIEnv *env, jclass clazz)
{
return env->NewStringUTF("动态注册jni函数返回结果");
}
#define JNIREG_CLASS "com/jianjin33/demo2/MainActivity" // 指定要注册的类
// JNINativeMethod结构体就是文章开头介绍的
static JNINativeMethod gMethods[] = {
{ "stringFromJni2", "()Ljava/lang/String;", (void*)native_stringFromJni2}, // 绑定
};
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
LOGI("%s","开始走JNI_OnLoad");
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) { // 注册
return -1;
}
result = JNI_VERSION_1_6;
return result;
}
java代码调用jni
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI(); // 静态注册方式
public native String stringFromJNI2(); // 动态注册方式
两种方式的对比
静态注册:根据函数名来建立Java函数和JNI函数之间的关联关系,要求JNI层函数的名字必须遵守特定的格式,所以书写起来比较长,并且,初次调用本地函数时要根据函数名搜索JNI层函数来建立关联关系,会影响运行效率。
动态注册:扩展性较好,避免了静态注册的不足之处。
参考博客
so库加载System.loadLibrary流程分析