且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《Android的设计与实现:卷I》——第2章 2.2.3Log系统的JNI方法注册

更新时间:2022-09-14 11:54:13

2.2.3 Log系统的JNI方法注册

JNI层已经实现了Java层声明的Native方法。可这两个方法又是如何联系在一起的呢?我们接着分析android_util_Log.cpp的源码。定位到以下部分:
static JNINativeMethod gMethods[] = {
{ "isLoggable", "(Ljava/lang/String;I)Z",

      (void) android_util_Log_isLoggable },

{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I",

      (void*) android_util_Log_println_native },

};
这里定义了一个数组gMethods,用来存储JNINativeMethod类型的数据。
可以在jni.h文件中找到JNINativeMethod的定义:

《Android的设计与实现:卷I》——第2章 2.2.3Log系统的JNI方法注册

可见,JNINativeMethod是一个结构体类型,保存了声明函数和实现函数的一一对应关系。

下面分析gMethods[0]中存储的对应信息:
{ "isLoggable", "(Ljava/lang/String;I)Z", (void) android_util_Log_isLoggable }
Java层声明的Native函数名为isLoggable。
Java层声明的Native函数的签名为(Ljava/lang/String;I)Z。
JNI层实现方法的指针为(void) android_util_Log_isLoggable。

至此,我们给出了Java层方法和JNI层方法的对应关系。可如何告诉虚拟机这种对应关系呢?
继续分析android_util_Log.cpp源码。定位到以下部分:
int register_android_util_Log(JNIEnv env)
{

jclass clazz = env->FindClass("android/util/Log");

levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz,

      "DEBUG", "I"));
……
return AndroidRuntime::registerNativeMethods(env, "android/util/Log",  
       gMethods, NELEM(gMethods));
       

这个函数最后调用了AndroidRuntime::registerNativeMethods,并将gMethods数组、Java层类名以及一个JNIEnv类型的指针一同传给registerNativeMethods。

初次接触JNI的读者可能有这样的疑问:这是在做什么?JNIEnv是什么?为什么找遍android_util_Log.cpp源码都没找到这个函数是在哪里调用的?

我们先来分析这个函数是做什么的。

第一个问题:registerNativeMethods函数的作用是什么?

既然registerNativeMethods是AndroidRuntime中定义的函数,打开AndroidRuntime.cpp文件,定位到registerNativeMethods函数,其代码如下:
int AndroidRuntime::registerNativeMethods(JNIEnv env,

const char className, const JNINativeMethod gMethods, int numMethods)

{

return jniRegisterNativeMethods(env, className, gMethods, numMethods);

}
这里仅仅是对jniRegisterNativeMethods的封装,可以在头文件JNIHelp.h中找到该方法的定义:
//注册特定类的一个或多个Native方法

int jniRegisterNativeMethods(C_JNIEnv env, const char className,
const JNINativeMethod gMethods, int numMethods);
接着看这个方法是怎么实现的。打开JNIHelp.cpp,代码如下:
extern "C" int jniRegisterNativeMethods(C_JNIEnv env, const char* className,

   const JNINativeMethod* gMethods, int numMethods)

{

JNIEnv e = reinterpret_cast<JNIEnv*>(env);
 scoped_local_ref<jclass> c(env, findClass(env, className));
……
if ((env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
   LOGE("RegisterNatives failed for '%s', aborting", className);
   abort();
}
return 0;

}
这里最终调用了JNIEnv的RegisterNatives函数,将gMethods中存储的方法关联信息传递给Dalvik虚拟机。在jni.h中找到RegisterNatives的方法原型如下:
jint RegisterNatives(JNIEnv env, jclass clazz,? const JNINativeMethod *methods,

 jint nMethods);
 

其作用是向clazz参数指定的类注册本地方法。这样,虚拟机就得到了Java层和JNI层之间的对应关系,就可以实现Java和C/C++代码的互操作了。

第二个问题:JNIEnv是什么?

为了不打断读者的思路,将在下节详细介绍JNIEnv是什么。这里读者只需要知道,JNIEnv是一个指针,指向了一组JNI函数,通过这组函数可以在JNI层操作Java对象,以此实现Java层和native层互操作。

第三个问题:register_android_util_Log函数是在哪里调用的?

这个问题涉及JNI部分代码在系统启动过程中是如何加载的,这已经超出了本章的知识范围,我们将在启动篇详细介绍这个过程。在这里,读者只需要知道这个函数是在系统启动过程中通过AndroidRuntime.cpp的register_jni_procs方法执行的,进而调用到register_android_util_Log将这种函数映射关系注册给Dalvik虚拟机的。

注意 使用JNI有两种方式:一种是遵守JNI规范的函数命名规范,建立声明函数和实现函数之间的对应关系;另一种是就是Log系统中采用的函数注册方式。应用层多采用第一种方式,应用框架层多采用第二种方式。

至此,Log系统就可以正常使用JNI提供的接口访问系统底层提供的服务了。下面,将对Log系统中涉及的JNI技术进行详细讲解。