涉及源码(Android 4.4.2):
/frameworks/base/core/jni/AndroidRuntime.cpp
/libnativehelper/JNIHelp.cpp
frameworks/base/media/jni/android_media_MediaPlayer.cpp
/dalvik/vm/Native.cpp
主动注册原理
AndroidRunTime类提供了一个registerNativeMethods函数可以完成注册工作
/frameworks/base/core/jni/AndroidRuntime.cpp
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
它调用的是jniRegisterNativeMethods方法,我们可以详细查看
/libnativehelper/JNIHelp.cpp
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast(env);
scoped_local_ref c(env, findClass(env, className));
if (c.get() == NULL) {
char* msg;
asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className);
e->FatalError(msg);
}
// Method->nativeFunc指向dvmCallJNIMethod,Method->insns存储真正的native函数指针
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
char* msg;
asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
e->FatalError(msg);
}
return 0;
}
它的作用就是将Method的nativeFunc指向dvmCallJNIMethod,当java层调用native函数的时候会进入这个函数。而真正的native函数指针则存储在Method->insns中。
调用动态注册的时机
Java层通过System.loadLibrary()加载完JNI动态库之后,会查找动态库中的JNI_OnLoad()方法,如果存在该方法,就调用它。
调用完JNI_OnLoad()方法之后,主动动态注册的过程就完成了,所以如果希望主动进行动态注册,就必须实现JNI_OnLoad()方法,并在该方法中进行方法的注册。
下面以MediaPlayer为例:
frameworks/base/media/jni/android_media_MediaPlayer.cpp
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
// 自己写的一个注册方法
if (register_android_media_MediaPlayer(env) < 0) {
goto bail;
}
...
}
static int register_android_media_MediaPlayer(JNIEnv *env)
{
// 可以看到实质调用的就是AndroidRuntime的registerNativeMethods方法
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
其中gMethods,记录java层和C/C++层方法的一一映射关系。
static JNINativeMethod gMethods[] = {
{"prepare", "()V", (void *)android_media_MediaPlayer_prepare},
{"_start", "()V", (void *)android_media_MediaPlayer_start},
{"_stop", "()V", (void *)android_media_MediaPlayer_stop},
{"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo},
{"_release", "()V", (void *)android_media_MediaPlayer_release},
{"native_init", "()V", (void *)android_media_MediaPlayer_native_init},
...
};
这里涉及到结构体JNINativeMethod,其定义在jni.h文件:
typedef struct {
const char* name; //Java层native函数名
const char* signature; //Java函数签名,记录参数类型和个数,以及返回值类型
void* fnPtr; //Native层对应的函数指针
} JNINativeMethod;
上面就完成了主动注册过程。
被动注册过程原理
被动注册方法就是通过javah来得到native层函数的函数名,这样就默认是将java层native函数跟底层的c++函数进行了映射,具体原理如下。
在Dalvik中,在对类进行加载并且解析相应的函数的时候,如果函数为native函数,则会把Method->nativeFunc设置为dvmResolveNativeMethod。
当我调用对应native函数的时候,首先会执行dvmResolveNativeMethod方法。
/dalvik/vm/Native.cpp
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));
}
// 1、从虚拟机内部的native方法中查找
DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method);
if (infunc != NULL) {
// 如果找到,则直接处理调用
DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc;
dvmSetNativeFunc((Method*) method, dfunc, NULL);
dfunc(args, pResult, method, self);
return;
}
// 2、从虚拟机中加载的so文件中查找
void* func = lookupSharedLibMethod(method);
if (func != NULL) {
/* found it, point it at the JNI bridge and then call it */
// 如果找到,则更新Method->nativeFunc和Method->insns,跟主动注册一样,然后调用对应的native函数
dvmUseJNIBridge((Method*) method, func);
(*method->nativeFunc)(args, pResult, method, self);
return;
}
}
现在重点来看看从虚拟机中加载的so文件中查找的过程。具体看lookupSharedLibMethod方法。
static void* lookupSharedLibMethod(const Method* method)
{
// 首先检查是否虚拟机存在so文件
if (gDvm.nativeLibs == NULL) {
ALOGE("Unexpected init state: nativeLibs not ready");
dvmAbort();
}
// 如果存在,则对so文件进行查找
return (void*) dvmHashForeach(gDvm.nativeLibs, findMethodInLib,
(void*) method);
}
具体来看看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;
// 1、首先进行一个包名、类名和函数名的拼接来构造该方法
// 就是我们通常通过javah来生成头文件的时候对应的方法名
preMangleCM =
createJniNameString(meth->clazz->descriptor, meth->name, &len);
if (preMangleCM == NULL)
goto bail;
mangleCM = mangleString(preMangleCM, len);
if (mangleCM == NULL)
goto bail;
// 2、通过dlsym来对该函数查找
func = dlsym(pLib->handle, mangleCM);
// 3、如果没有查找到,则通过包名、类名、函数名和参数名来构造这个方法,继续查找
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);
ALOGV("+++ calling dlsym(%s)", mangleCMSig);
func = dlsym(pLib->handle, mangleCMSig);
if (func != NULL) {
ALOGV("Found '%s' with dlsym", mangleCMSig);
}
} else {
ALOGV("Found '%s' with dlsym", mangleCM);
}
bail:
free(preMangleCM);
free(mangleCM);
free(mangleSig);
free(mangleCMSig);
return (int) func;
}
参考文章:
http://www.infoq.com/cn/articles/android-in-depth-dalvik