声明
- 前阶段在项目中使用了Android的JNI技术,在此文中做些技术知识总结。
- 本文参考了一些书籍的若干章节,比如《Android进阶解密-第9章-JNI原理》、《深入理解Android虚拟机-第4章-分析JNI》、《深入理解Android系统-第2章-分析JNI》、《Android NDK Beginner_'s Guide》等
- 之所以会参考这么多书籍的原因是我在看某一技术问题时,习惯参考不同书籍作者的看法,毕竟不同的书都有自己的侧重点,而且不同的作者对同一技术问题的理解深度也不同。
- 本文使用的代码时LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客:如何下载Nexus5的LineageOS14.1(cm-14.1)系统源码并编译、刷机
3 Java层和JNI层的数据类型转换和方法签名
进入到源码路径:
vim ~/LineageOS/frameworks/base/media/jni/android_
media_MediaRecorder.cpp,查看函数android_media_MediaRecorder_native_setup:
static void
android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jstring packageName, jstring opPackageName)
{
ALOGV("setup");
ScopedUtfChars opPackageNameStr(env, opPackageName);
sp<MediaRecorder> mr = new MediaRecorder(String16(opPackageNameStr.c_str()));
if (mr == NULL) {
jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
return;
}
if (mr->initCheck() != NO_ERROR) {
jniThrowException(env, "java/lang/RuntimeException", "Unable to initialize media recorder");
return;
}
// create new listener and give it to MediaRecorder
sp<JNIMediaRecorderListener> listener = new JNIMediaRecorderListener(env, thiz, weak_this);
mr->setListener(listener);
// Convert client name jstring to String16
const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
env->GetStringChars(packageName, NULL));
jsize rawClientNameLen = env->GetStringLength(packageName);
String16 clientName(rawClientName, rawClientNameLen);
env->ReleaseStringChars(packageName,
reinterpret_cast<const jchar*>(rawClientName));
// pass client package name for permissions tracking
mr->setClientName(clientName);
setMediaRecorder(env, thiz, mr);
}
其中jobject、jstring类型参数都是JNI层的数据类型,Java层数据类型到JNI层就要转换为JNI层数据结构。包括基本数据类型和引用数据类型
3.1 Java层至JNI层基本数据类型的转换
从转换表中可以看出Java层基本数据类型转换到JNI层只需将数据类型前加个“j”即可(除了void类型)。
3.2 Java层至JNI层引用数据类型的转换
Java | Native | 签名(以;结尾) |
---|---|---|
所有对象 | jobject | L+classname +; |
Class | jclass | Ljava/lang/Clas; |
String | jstring | Ljava/lang/String; |
Trowable | jthrowable | Ljava/lang/Throwable; |
Object[] | jobjectArray | [L+classname +; |
byte[] | jbyteArray | [B |
char[] | jcharArray | [C |
double[] | jdoubleArray | [D |
float[] | jfloatArray | [F |
int[] | jintArray | [I |
short[] | jshortArray | [S |
long[] | jlongArray | [J |
boolean[] | jbooleanArray | [Z |
以~/LineageOS/frameworks/base/media/java/android/media/
MediaRecorder.java中的native_setup方法为例:
private native final void native_setup(Object mediarecorder_this,
String clientName, String opPackageName) throws IllegalStateException;
对应在~/LineageOS/frameworks/base/media/jni/android_
media_MediaRecorder.cpp,查看函数android_media_MediaRecorder_native_setup:
static void
android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jstring packageName, jstring opPackageName)
{
...省略n行...
}
可以发现:Object类型转换为jobject类型,String类型转换为jstring类型。
3.3 方法签名
进入到源码路径: vim ~/LineageOS/frameworks/base/media/jni/
android_media_MediaRecorder.cpp,在数组gMethods[]中可看到签名信息:
static const JNINativeMethod gMethods[] = {
{"setCamera", "(Landroid/hardware/Camera;)V", (void *)android_media_MediaRecorder_setCamera},
{"setVideoSource", "(I)V", (void *)android_media_MediaRecorder_setVideoSource},
{"setAudioSource", "(I)V", (void *)android_media_MediaRecorder_setAudioSource},
{"setOutputFormat", "(I)V", (void *)android_media_MediaRecorder_setOutputFormat},
{"setVideoEncoder", "(I)V", (void *)android_media_MediaRecorder_setVideoEncoder},
{"setAudioEncoder", "(I)V", (void *)android_media_MediaRecorder_setAudioEncoder},
{"setParameter", "(Ljava/lang/String;)V", (void *)android_media_MediaRecorder_setParameter},
{"_setOutputFile", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaRecorder_setOutputFileFD},
{"setVideoSize", "(II)V", (void *)android_media_MediaRecorder_setVideoSize},
{"setVideoFrameRate", "(I)V", (void *)android_media_MediaRecorder_setVideoFrameRate},
{"setMaxDuration", "(I)V", (void *)android_media_MediaRecorder_setMaxDuration},
{"setMaxFileSize", "(J)V", (void *)android_media_MediaRecorder_setMaxFileSize},
{"_prepare", "()V", (void *)android_media_MediaRecorder_prepare},
{"getSurface", "()Landroid/view/Surface;", (void *)android_media_MediaRecorder_getSurface},
{"getMaxAmplitude", "()I", (void *)android_media_MediaRecorder_native_getMaxAmplitude},
{"start", "()V", (void *)android_media_MediaRecorder_start},
{"stop", "()V", (void *)android_media_MediaRecorder_stop},
{"pause", "()V", (void *)android_media_MediaRecorder_pause},
{"resume", "()V", (void *)android_media_MediaRecorder_resume},
{"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset},
{"release", "()V", (void *)android_media_MediaRecorder_release},
{"native_init", "()V", (void *)android_media_MediaRecorder_native_init},
{"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V",
(void *)android_media_MediaRecorder_native_setup},
{"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize},
{"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
};
简单地说,存在签名的原因就是Java语言的方法是可以重载的,重载的方法名字相同而参数不同,所以JNI仅通过方法名无法确定对应的是重载的哪个方法,必须要参数签名来辅助其关联。
4 JNI函数中的关键参数:JNIEnv 指针
JNIEnv指针是Native世界中Java环境的代表,通过该指针Native世界就可以访问Java世界的代码进行操作了,其具有以下主要特点及作用:
- JNIEnv *只对创建它的线程有效,不能跨线程传递;
- 通过JNIEnv *可调用Java的方法;
- 通过JNIEnv *可操作Java中的变量和对象;
进入到源码路径: vim ~/LineageOS/libnativehelper/include/nativehelper/jni.h,查看JNIEnv结构体的声明:
#if defined(__cplusplus)
//C++中JNIEnv的类型
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
//C中JNIEnv的类型
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
其中,JavaVM虚拟机在JNI层的代表,在一个虚拟机进程中只有一个JavaVM,该进程的所有线程都可以使用此JavaVM。
其实追踪_JNIEnv结构体代码,发现其最终使用的还是JNINativeInterface结构体:
/*
* C++ object wrapper.
*
* This is usually overlaid on a C struct whose first element is a
* JNINativeInterface*. We rely somewhat on compiler behavior.
*/
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
//这里调用的依然是JNINativeInterface结构体指针
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jint GetVersion()
{ return functions->GetVersion(this); }
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen)
{ return functions->DefineClass(this, name, loader, buf, bufLen); }
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
...省略n行...
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
{ return functions->GetFieldID(this, clazz, name, sig); }
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
{ return functions->GetMethodID(this, clazz, name, sig); }
}
所以,无论是C或C++,JNIEnv的类型都使用的JNINativeInterface机构。
继续在源码路径: vim ~/LineageOS/libnativehelper/include/nativehelper/jni.h,查看JNINativeInterface结构体的声明:
/*
* Table of interface function pointers.
*/
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
jint (*GetVersion)(JNIEnv *);
jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
jsize);
jclass (*FindClass)(JNIEnv*, const char*);
jmethodID (*FromReflectedMethod)(JNIEnv*, jobject);
jfieldID (*FromReflectedField)(JNIEnv*, jobject);
/* spec doesn't show jboolean parameter */
jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);
void (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong);
void (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat);
void (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble);
...省略n行...
/* added in JNI 1.6 */
jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
};
其中包含了很多函数指针都与JNIEnv结构体中的函数指针对应,这些函数指针就是在编写JNI程序时所需调用的。通过这些函数指针,就能定位到虚拟机中的JNI函数表,从而实现JNI层在虚拟机中的函数调用,这样JNI层就可以调用Java世界代码。
4.1 _JNIEnv结构体中定义的函数返回值
_JNIEnv结构体中定义的函数返回值有多种,包括:jobject、jboolean、jstring、jmethodID、jfieldID等,其中jmethodID、jfieldID分别用以代表Java中的成员方法和成员变量。
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
{ return functions->GetFieldID(this, clazz, name, sig); }
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
{ return functions->GetMethodID(this, clazz, name, sig); }
}
其中jclass代表Java类、name代表成员方法或成员变量的名字、sig为此方法和变量的签名。
进入到源码路径: vim ~/LineageOS/frameworks/base/media/jni/
android_media_MediaRecorder.cpp中,查看android_media_MediaRecorder_
native_init函数
// This function gets some field IDs, which in turn causes class initialization.
// It is called from a static block in MediaRecorder, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{
jclass clazz;
//利用FindClass找到Java层的MediaRecorder的Class对象,赋予clazz。
clazz = env->FindClass("android/media/MediaRecorder");
if (clazz == NULL) {
return;
}
//利用GetFieldID找到Java层MediaRecorder中名为mNativeContext的成员变量
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
//利用GetFieldID找到Java层MediaRecorder中名为mSurface的成员变量
fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
if (fields.surface == NULL) {
return;
}
jclass surface = env->FindClass("android/view/Surface");
if (surface == NULL) {
return;
}
//利用GetStaticMethodID找到Java层MediaRecorder中名为postEventFromNative的静态方法,赋予fields.post_event
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.post_event == NULL) {
return;
}
这里面的field是fields_t类型的结构体,在MediaRecorder框架的JNI层android_media_MediaRecorder_native_init函数将jfieldID和jmethodID类型的变量保存下来,已被后面的调用高效利用。
4.2 jmethodID、jfieldID使用方法
看源码…
5 JNI中的引用类型(本地引用、全局引用、弱全局引用)
待补充…