注意JNI与NDK的区别:NDK是为了便于开发基于JNI的应用而提供的一套开发和编译工具集,而JNI则是一套编程接口,可以运用在应用层,也可以运用在应用框架层,以实现Java与本地代码的互操作。
JNI编程模型的结构可以概括为以下三个步骤:
* Java层声明native方法
* JNI层实现Java层声明的native方法,在JNI层可以调用底层库或者回调Java层方法。这部分将被编译为动态库(so文件)供系统加载
* 加载JNI层代码编译后生成的共享库
Log系统Java层中Log.java文件中定义了两个native方法
/**
* Send a {@link #DEBUG} log message.
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public static int d(String tag, String msg) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
/**
* Checks to see whether or not a log for the specified tag is loggable at the specified level.
*
* The default level of any tag is set to INFO. This means that any level above and including
* INFO will be logged. Before you make any calls to a logging method you should check to see
* if your tag should be logged. You can change the default level by setting a system property:
* 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>'
* Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
* turn off all logging for your tag. You can also create a local.prop file that with the
* following in it:
* 'log.tag.<YOUR_LOG_TAG>=<LEVEL>'
* and place that in /data/local.prop.
*
* @param tag The tag to check.
* @param level The level to check.
* @return Whether or not that this is allowed to be logged.
* @throws IllegalArgumentException is thrown if the tag.length() > 23
* for Nougat (7.0) releases (API <= 23) and prior, there is no
* tag limit of concern after this API level.
*/
public static native boolean isLoggable(String tag, int level);
······
/** @hide */ public static native int println_native(int bufID, int priority, String tag, String msg);
打开android_util_Log.cpp文件
#define LOG_NAMESPACE “log.tag.”
#define LOG_TAG “Log_println”
······
#include "jni.h" // 符合JNI规范的头文件
#include "JNIHelp.h" // Android为更好地支持JNI提供的头文件
#include "utils/misc.h"
#include "core_jni_helpers.h"
#include "android_util_Log.h"
namespace android {
······
/**
* JNI方法增加了JNIEnv和jobject两个参数,其余参数和返回值只是将Java参数
* 映射成JNI数据类型,然后通过调用本地库和JNIEnv提供的JNI函数处理数据,
* 最后返回给Java层
**/
static jboolean isLoggable(const char* tag, jint level) {
return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
}
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
if (tag == NULL) {
return false;
}
const char* chars = env->GetStringUTFChars(tag, NULL);
if (!chars) {
return false;
}
jboolean result = isLoggable(chars, level);
env->ReleaseStringUTFChars(tag, chars);
return result;
}
······
/*
* In class android.util.Log:
* public static native int println_native(int buffer, int priority, String tag, String msg)
*/
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
const char* tag = NULL;
const char* msg = NULL;
if (msgObj == NULL) {
jniThrowNullPointerException(env, "println needs a message");
return -1;
}
if (bufID < 0 || bufID >= LOG_ID_MAX) {
jniThrowNullPointerException(env, "bad bufID");
return -1;
}
if (tagObj != NULL)
tag = env->GetStringUTFChars(tagObj, NULL);
msg = env->GetStringUTFChars(msgObj, NULL);
int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
// 调用JNI函数释放资源
if (tag != NULL)
env->ReleaseStringUTFChars(tagObj, tag);
env->ReleaseStringUTFChars(msgObj, msg);
return res;
}
······
int register_android_util_Log(JNIEnv* env)
{
jclass clazz = FindClassOrDie(env, "android/util/Log");
levels.verbose = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "VERBOSE", "I"));
levels.debug = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "DEBUG", "I"));
levels.info = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "INFO", "I"));
levels.warn = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "WARN", "I"));
levels.error = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ERROR", "I"));
levels.assert = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ASSERT", "I"));
return RegisterMethodsOrDie(env, "android/util/Log", gMethods, NELEM(gMethods));
}
}; // namespace android
JNI层的实现方法只是根据一定的规则与Java层声明的方法做了一个映射,然后通过调用本地块函数或者JNIEnv提供的JNI函数响应Java层的调用
定位到android_util_Log.cpp的源码
/*
* JNI registration.
*/
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "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 },
{ "logger_entry_max_payload_native", "()I", (void*) android_util_Log_logger_entry_max_payload_native },
};
GMethods用来存储JNINativeMethod类型的数据
typedef struct {
const char* name; // Java层声明的native函数的函数名
const char* signature; // Java函数的签名,依据JNI的签名规则
void* fnPtr; // 函数指针,指向JNI的实现方法
} JNINativeMethod;
JNINativeMethod是一个结构体类型,保存了Java层声明函数和JNI实现函数的一一对应的关系。
可是如何告诉虚拟机这种对应的关系?我们继续看android_util_Log.cpp中的register_android_util_Log函数
int register_android_util_Log(JNIEnv* env)
{
jclass clazz = env->FindClass("android/util/Log");
······
levels.debug = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "DEBUG", "I"));
······
return AndroidRuntime::registerMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
}
这里我们来解析三个问题
* registerMethods函数的作用是什么?
定位到函数内部
/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
在JNIHelp.h和JNIHelp.cpp中找到函数jniRegisterNativeMethods的实现
int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast(env);
ALOGV("Registering %s's %d native methods...", className, numMethods);
scoped_local_ref c(env, findClass(env, className));
if (c.get() == NULL) {
char* tmp;
const char* msg;
if (asprintf(&tmp,
"Native registration unable to find class '%s'; aborting...",
className) == -1) {
// Allocation failed, print default warning.
msg = "Native registration unable to find class; aborting...";
} else {
msg = tmp;
}
e->FatalError(msg);
}
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
char* tmp;
const char* msg;
if (asprintf(&tmp, "RegisterNatives failed for '%s'; aborting...", className) == -1) {
// Allocation failed, print default warning.
msg = "RegisterNatives failed; aborting...";
} else {
msg = tmp;
}
e->FatalError(msg);
}
return 0;
}
这里最终调用了JNIEnv的RegisterNatives函数,将gMethods中存储的方法关联信息传递给虚拟机。其作用就是向clazz参数指定的类注册本地方法,这样虚拟机就得到了Java层和JNI层之间的对应关系,就可以实现Java和C/C++代码的互操作了
* register_android_util_Log函数是在哪调用的
这个函数是在系统启动过程中通过AndroidRuntime.cpp的register_jni_procs方法执行的,进而调用到register_android_util_Log函数将这种映射关系注册给Dalvik虚拟机
建立Java层声明函数和JNI层实现函数之间的对应关系有两种方式
* 遵守JNI方法命名规范
* 采用函数注册方式
应用层多采用第一种,框架层多采用第二种。
我们先看看JNIEnv的体系结构
从图中我们可以看出JNIEnv首先指向一个线程相关的结构,该结构又指向一个指针数组,在这个指针数组中每一个元素最终又指向一个JNI函数,所有可以通过JNIEnv去调用JNI函数。JNIEnv是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境
JavaVM和JNIEnv的关系
* JavaVM是Java虚拟机在JNI层的代表,JNI全局只有一个
* JNIEnv是一个线程相关的结构体,每个线程都有一个,JNI中可能有多个
相关源码就不看了,我们直接给出结论
* 在C++中:JNIEnv就是struct _JNIEnv,JNIEnv * env等价于struct _JNIEnv * env,在调用JNI函数的时候,只需要env->FindClass就会直接调用JNINativeInterface结构体里定义的函数指针,而无需首先对env解引用
* 在C中:JNIEnv就是const struct JNINativeInterface * ,JNIEnv * env实际上等价于const struct JNINativeInterface * env,因此要得到JNINativeInterface结构体内的函数指针就必须先对env解引用得到( env),即const struct JNINativeInterface ,这个指针才是真正指向JNINativeInterface结构体的指针,然后再通过它调用具体的JNI函数,因此需要( env)->FindClass这样调用
JNIEnv只在当前线程中有效。本地方法不能讲JNIEnv从一个线程传递到另一个线程,相同的Java线程对本地方法多次调用时传递的JNIEnv是相同的。但是一个本地方法可以被不同的Java线程调用,因此可以接受不同的JNIEnv
JNI方法由以下几个部分组成:
* 前缀: Java_
* 类的全限定名,用下划线进行分隔(_):com_lms_jni_JniTest
* 方法名:getTestString
* jni函数指定第一个参数: JNIEnv *
* jni函数指定第二个参数: jobject
* 实际Java参数: jstring, jint ….
* 返回值的参数 : jstring, jint….
如果采用函数注册的方式,可以不遵守上述规则
JNI的签名规则如下:
(参数1类型签名参数2类型签名···参数n类型签名)返回值类型签名
注意:中间没有任何符号
注意:类的签名规则是:”L+全限定类名+;”,其中全限定类名由”/”分隔。例如
long fun(int n, String str, int[] arr);
其方法签名为:(ILjava/lang/String;[I)J
JNI提供的类和对象操作的方法有很多,常用的有两个:FindClass和GetObjectClass,在C和C++中有不同的函数原型:
* C++:
jclass FindClass(const char * name);
jclass GetObjectClass(jobject obj);
* C:
jclass (* FindClass)(JNIEnv *, const char *);
jclass (* GetObjectClass)(JNIEnv *, jobject);
我们看看Log系统是如何操作Java对象的
jclass clazz = env->FindClass("android/util/Log");
我们还是来看看Log系统是如何操作成员变量的
jclass clazz = env->FindClass("android/util/Log");
levels.debug = env->GetStaticIntField(clazz, GetStaticFieldID(env, clazz, "DEBUG", "I"));
首先通过FindClass找到android/util/Log的类信息clazz,然后以clazz为参数调用GetStaticFieldId(clazz, “DEBUG”, “I”),其函数原型如下:
jfieldId GetStaticFieldId(jclass clazz, const char * name, const char * sig);
最后将返回的jfieldId传递给GetStaticIntField方法得到android/util/Log.java的成员变量DEBUG的值
JNI操Java层的方法类似,流程是
FindClass->GetMethodId->返回jMethodId->CallMethod
注:本文参考《Android设计与实现卷I(杨云君)》