http://liview.cn/discuz/forum.php?mod=viewthread&tid=5 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即可实现用新的方法来替换老的方法。 当然,这是仅对当前进程有效,不影响其他进程。 |
|