http://blog.csdn.net/imyfriend/article/details/9117917
Java JNI有两种方法,一种是通过javah,获取一组带签名函数,然后实现这些函数。这种方法很常用,也是官方推荐的方法,本文不再详述,重点说明一下JNI_OnLoad方法。 当在系统中调用System.loadLibrary函数时,该函数会找到对应的动态库,然后首先试图找到"JNI_OnLoad"函数,如果该函数存在,则调用它。 JNI_OnLoad可以和JNIEnv的registerNatives函数结合起来,实现动态的函数替换。 下面用一个简单的例子来说明 java类声明 创建目录mj/jnitest,并新建两个文件MyObject.java和JniTest.java MyObject.java
package mj.jnitest; class MyObject { static { System.loadLibrary("jni"); //这是加载使用javah规定风格实现的库 } //下面定义两个native函数 public native void func1(); public native void func2(); }
复制代码
JniTest.java
package mj.jnitest; class JniTest { // static { //这是一种静态的加载方式,可以完全工作;但是下面我们要用更灵活的方式进行 // System.loadLibrary("jni2"); // } public static void main(String[] args) { MyObject obj = new MyObject(); //在fun2函数替换之前,先进行一次调用,会调研jni1中的函数 obj.func1(); obj.func2(); //用JNI_OnLoad进行主动注册 System.loadLibrary("jni2"); obj.func1(); obj.func2(); //func2已经被jni2中的函数替换 } };
复制代码
在JniTest.java中,有两个动态库jni1和jni2会被同时加载。jni1在MyObject类被链接时被加载;jni2则在MyObject的实例obj运行时被加载。首先看看他的输出结果:
$ java mj.jnitest.JniTest
--- func1 called in version 1
--- func2 called in version 1
--- func1 called in version 1
--- func2 called in version 2
从结果看出,前两行调用obj.func1和obj.func2,都是jni1中的函数,所以打印的是version 1;
而加载了jni2后,obj.func1函数仍旧是jni1中的,而func2就变成了jni2中的了。
下面看下jni1和jni2的源代码
jni1的源代码mj_jnitest_MyObject.c
#include <stdio.h> #include <stdlib.h> #include "mj_jnitest_MyObject.h" /* * Class: mj_jnitest_MyObject * Method: func1 * Signature: ()V */ JNIEXPORT void JNICALL Java_mj_jnitest_MyObject_func1 (JNIEnv *env, jobject jobj) { printf("--- func1 called in version 1\n"); } /* * Class: mj_jnitest_MyObject * Method: func2 * Signature: ()V */ JNIEXPORT void JNICALL Java_mj_jnitest_MyObject_func2 (JNIEnv *env, jobject jobj) { printf("--- func2 called in version 1\n"); }
复制代码
斜体部分正是打印的内容
jni2的源代码jni2.c(部分)
include <stdlib.h>
#include <jni.h> static void JNICALL func2 (JNIEnv *env, jobject jobj) { printf("--- func2 called in version 2\n"); }
复制代码
....
JNI_OnLoad的使用方法
先看一下jni2.c的完整源代码,并注意注释
#include <stdio.h> #include <stdlib.h> #include <jni.h> //jni的主要头文件 static void JNICALL func2 //函数名字可以随便取,不过参数一定要和javah生成的函数的参数一致,包括返回值 (JNIEnv *env, jobject jobj) { printf("--- func2 called in version 2\n"); } static const JNINativeMethod gMethods[] = { //定义批量注册的数组,是注册的关键部分 {"func2", "()V", (void*)func2} // func2是在java中声明的native函数名,"()V"是函数的签名,可以通过javah获取。 }; JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void *reserved) //这是JNI_OnLoad的声明,必须按照这样的方式声明 { JNIEnv* env = NULL; //注册时在JNIEnv中实现的,所以必须首先获取它 jint result = -1; if((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4) != JNI_OK) //从JavaVM获取JNIEnv,一般使用1.4的版本 return -1; jclass clazz; static const char* const kClassName="mj/jnitest/MyObject"; clazz = (*env)->FindClass(env, kClassName); //这里可以找到要注册的类,前提是这个类已经加载到java虚拟机中。 这里说明,动态库和有native方法的类之间,没有任何对应关系。 if(clazz == NULL) { printf("cannot get class:%s\n", kClassName); return -1; } if((*env)->RegisterNatives(env, clazz,gMethods, sizeof(gMethods)/sizeof(gMethods[0]))!= JNI_OK) //这里就是关键了,把本地函数和一个java类方法关联起来。不管之前是否关联过,一律把之前的替换掉! { printf("register native method failed!\n"); return -1; } return JNI_VERSION_1_4; //这里很重要,必须返回版本,否则加载会失败。 }
复制代码
对他进行编译后,得到一个libjni2.so。
C++用法说明
上面的用法是c语言中的用法,在C++中更简单。
JavaVM和JNIEnv都是经过简单封装的类,可以直接按照如下方式调用
(vm->GetEnv((void**)&env, JNI_VERSION_1_4)
env->FindClass(kClassName);
env->RegisterNatives(clazz,gMethods, sizeof(gMethods)/sizeof(gMethods[0]))
Dalvik中动态库的原理简要分析
之所以出现这种结果,和jni的机制有关的,通过对Android中的Dalvik的分析,可以印证。
System.loadLibrary,也是一个native方法,它向下调用的过程是:
Dalvik/vm/native/java_lang_Runtime.cpp: Dalvik_java_lang_Runtime_nativeLoad ->
Dalvik/vm/Native.cpp:dvmLoadNativeCode
dvmLoadNativeCode
打开函数dvmLoadNativeCode,可以找到以下代码
bool result = true; void* vonLoad; int version; vonLoad = dlsym(handle, "JNI_OnLoad"); //获取JNI_OnLoad的地址 if (vonLoad == NULL) { //这是用javah风格的代码了,推迟解析 LOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader); } 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.) */ OnLoadFunc func = (OnLoadFunc)vonLoad; Object* prevOverride = self->classLoaderOverride; self->classLoaderOverride = classLoader; oldStatus = dvmChangeStatus(self, THREAD_NATIVE); if (gDvm.verboseJni) { LOGI("[Calling JNI_OnLoad for \"%s\"]", pathName); } version = (*func)(gDvmJni.jniVm, NULL); //调用JNI_OnLoad,并获取返回的版本信息 dvmChangeStatus(self, oldStatus); self->classLoaderOverride = prevOverride; if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6) //对版本进行判断,这是为什么要返回正确版本的原因 { LOGW("JNI_OnLoad returned bad version (%d) in %s %p", version, pathName, classLoader); /* * 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. */ result = false; } else { if (gDvm.verboseJni) { LOGI("[Returned from JNI_OnLoad for \"%s\"]", pathName); } } 上面的代码说明,JNI_OnLoad是一种更加灵活,而且处理及时的机制。 用javah风格的代码,则推迟解析,直到需要调用的时候才会解析。这样的函数,是dvmResolveNativeMethod(dalvik/vm/Native.cpp) dvmResolveNativeMethod dvmResolveNativeMethod是在一种延迟解析机制,它的代码是 void dvmResolveNativeMethod(const u4* args, JValue* pResult, const Method* method, Thread* self) { ClassObject* clazz = method->clazz; /* * If this is a static method, it could be called before the class * has been initialized. */ if (dvmIsStaticMethod(method)) { if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) { assert(dvmCheckException(dvmThreadSelf())); return; } } else { assert(dvmIsClassInitialized(clazz) || dvmIsClassInitializing(clazz)); } /* start with our internal-native methods */ DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method); if (infunc != NULL) { /* resolution always gets the same answer, so no race here */ IF_LOGVV() { char* desc = dexProtoCopyMethodDescriptor(&method->prototype); LOGVV("+++ resolved native %s.%s %s, invoking", clazz->descriptor, method->name, desc); free(desc); } if (dvmIsSynchronizedMethod(method)) { LOGE("ERROR: internal-native can't be declared 'synchronized'"); LOGE("Failing on %s.%s", method->clazz->descriptor, method->name); dvmAbort(); // harsh, but this is VM-internal problem } DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc; dvmSetNativeFunc((Method*) method, dfunc, NULL); dfunc(args, pResult, method, self); return; } /* now scan any DLLs we have loaded for JNI signatures */ void* func = lookupSharedLibMethod(method); //注意到,这里,是获取地址的地方 if (func != NULL) { /* found it, point it at the JNI bridge and then call it */ dvmUseJNIBridge((Method*) method, func); (*method->nativeFunc)(args, pResult, method, self); return; } IF_LOGW() { char* desc = dexProtoCopyMethodDescriptor(&method->prototype); LOGW("No implementation found for native %s.%s %s", clazz->descriptor, method->name, desc); free(desc); } dvmThrowUnsatisfiedLinkError(method->name); }
复制代码
lookupSharedLibMethod函数会调用到函数findMethodInLib,当然,不是直接调用,有兴趣的可以参考具体源码。
findMethodInLib是实现解析的:
static int findMethodInLib(void* vlib, void* vmethod)
{
const SharedLib* pLib = (const SharedLib*) vlib;
const Method* meth = (const Method*) vmethod;
char* preMangleCM = NULL;
char* mangleCM = NULL;
char* mangleSig = NULL;
char* mangleCMSig = NULL;
void* func = NULL;
int len;
if (meth->clazz->classLoader != pLib->classLoader) {
LOGV("+++ not scanning '%s' for '%s' (wrong CL)",
pLib->pathName, meth->name);
return 0;
} else
LOGV("+++ scanning '%s' for '%s'", pLib->pathName, meth->name);
/*
* First, we try it without the signature.
*/
preMangleCM =
createJniNameString(meth->clazz->descriptor, meth->name, &len);
if (preMangleCM == NULL)
goto bail;
mangleCM = mangleString(preMangleCM, len); //这里,把java的native方法的名字进行转换,生成和javah一致的名字
if (mangleCM == NULL)
goto bail;
LOGV("+++ calling dlsym(%s)", mangleCM);
func = dlsym(pLib->handle, mangleCM);
if (func == NULL) {
mangleSig =
createMangledSignature(&meth->prototype);
if (mangleSig == NULL)
goto bail;
mangleCMSig = (char*) malloc(strlen(mangleCM) + strlen(mangleSig) +3);
if (mangleCMSig == NULL)
goto bail;
sprintf(mangleCMSig, "%s__%s", mangleCM, mangleSig);
LOGV("+++ calling dlsym(%s)", mangleCMSig);
func = dlsym(pLib->handle, mangleCMSig); //dlsym清晰的表明,这里才是获取符号的地方。
if (func != NULL) {
LOGV("Found '%s' with dlsym", mangleCMSig);
}
} else {
LOGV("Found '%s' with dlsym", mangleCM);
}
bail:
free(preMangleCM);
free(mangleCM);
free(mangleSig);
free(mangleCMSig);
return (int) func;
}
实际上,无论是那种方式,从vm的代码中,都可以看出,这些符号可以放在任意的动态库中,只要确保他们调用了System.loadLibrary即可。
JNI_OnLoad函数,可以通过registerNatives,在任意时刻替换。
VM把native函数指针通过JNI Bridge,放到一个Method结构中,这个Method结构,最终会放在struct DvmGlobals gDvm;这个全局变量中。
由于是普通的全局变量,在java独立进程中保存,一旦该全局变量被修改,linux的copy-on-write机制启动,就会形成一个该进行独有的一个gDvm变量,从而和其他进行区分开。
利用JNI_OnLoad替换WebCore模块
在Android的WebViewCore类里,静态加载了
static { // Load libwebcore and libchromium_net during static initialization. // This happens in the zygote process so they will be shared read-only // across all app processes. try { System.loadLibrary("webcore"); System.loadLibrary("chromium_net"); } catch (UnsatisfiedLinkError e) { Log.e(LOGTAG, "Unable to load native support libraries."); } }
复制代码
注意到红字部分的说明,Android通过zygote进程,来孵化每个新启动的进程。
Android为了加快启动速度,把一些重要的类都放在了preloaded-classes中,这个列表,可以在Android源码的frameworks/base/preloaded-classes中找到,
也可以在frameworks.jar包中找到,就在最上层。
而webkit相关的类,也在这个proloaded-classes的列表中。它意味着,在android系统启动时,这些类就都会被加载到系统中。
但是,通过JNI_OnLoad机制,在浏览器的主Activiy中,只要加入
static { System.loadLibrary("mxwebcore"); }
复制代码
VM即可实现用新的方法来替换老的方法。
当然,这是仅对当前进程有效,不影响其他进程。