1. Native方法注册
native方法注册分为静态注册和动态注册,静态注册多用于NDK开发,动态注册多用于framework开发
1.1 静态注册
通过AS写一个简单的JniTest.java文件
public class JniTest {
static {
System.loadLibrary("jni-test");
}
public static void main(String[] args) {
JniTest jniTest = new JniTest();
System.out.println("JNI测试 " + jniTest.get());
jniTest.set("测试语句");
}
public native String get();
public native void set(String str);
}
进入项目的通过命名行
- 编译class文件 javac /Users/tangpeng/Downloads/android/Troll/app/src/main/java/com/troll/troll/jni/JniTest.java 生成JiniTest.class文件
- 通过javah命令导出JNI的头文件 通过终端进入tangpeng@tangpengdeMacBook-Pro java java这个文件夹下 然后javah com.troll.troll.jni.JniTest 生成com_troll_troll_jni_JniTest.h文件
.....
/*
* Class: com_troll_troll_jni_JniTest
* Method: get
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_troll_troll_jni_JniTest_get
(JNIEnv *, jobject);
/*
* Class: com_troll_troll_jni_JniTest
* Method: set
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_troll_troll_jni_JniTest_set
(JNIEnv *, jobject, jstring);
.....
get()方法被声明为Java_com_troll_troll_jni_JniTest_get(),其中
- Java开头,说明是在Java平台中国调用JNI方法
- com_troll_troll_jni_JniTest_get 指:包名+类名+方法名格式
- JNIEnv是Native世界中Java环境的代表,通过JNIEnv* 指针可以在Native世界中访问Java世界的代码进行操作,只在创建他的线程中有效,不能跨线程传递
- jclass、jobject、jstring是JNI的数据类型,对应Java的java.lang.Class实例、Object、String
静态注册:就是根据方法名,将Java方法和JNI函数建立关联,其实是保存JNI的函数方法指针,再猜调用get()方法直接使用这个函数指针 缺点:
- JNI层的函数名称过长
- 声明Native方法的类需要用javah生成头文件
- 初次调用Native方法时需要建立关联,影响效率
动态注册
JNI中有一种结构用来记录Java的Native方法和JNI方法的关联关系JNINativeMethod,它是在jni.h中被定义:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
例子: 系统的MediaRecorder采用的就是动态注册
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},
......
上面定义了一个JNINativeMethod类型的getMethod数组,里面存储是MediaRecorder的Native方法与JNI层函数的对应关系,
- setCamera 是Java层的Native方法,对应的JNI层的函数为android_media_MediaRecorder_setCamera
- (I)V是start方法的签名信息
只定义了JNINativeMethod类型的数组是没有用的,还需要注册它,注册函数为register_android_media_MediaRecorder(),调用的地方
frameworks base media jni android_media_MediaPlayer.cpp
....
if (register_android_media_MediaRecorder(env) < 0) {
ALOGE("ERROR: MediaRecorder native registration failed\n");
goto bail;
}
....
在JNI_OnLoad函数中调用了整个多媒体框架注册JNINativeMethod数组的函数,上面就是注册的函数
frameworks base media jni android_media_MediaRecorder.cpp
int register_android_media_MediaRecorder(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaRecorder", gMethods, NELEM(gMethods));
}
在register_android_media_MediaRecorder方法中返回了AndroidRuntime的registerNativeMethods函数,
frameworks base core jni AndroidRuntime.cpp
/*
* 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);
}
libnativehelper JNIHelp.cpp
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
.....
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函数来完成JNI的注册。
2. 数据类型的转换
基本数据类型转换
引用数据类型的转换
3. 方法签名(Signature)
方法签名是有签名格式组成的
static const JNINativeMethod gMethods[] = {
{"setParameter", "(Ljava/lang/String;)V", (void *)android_media_MediaRecorder_setParameter}
}
getMethods数组中存储了MediaRecorder的Native方法与JNI层函数的对应关系,其中"(I)V"和”(Ljava/lang/String;V)“都是方法签名 作用:
Java有重载方法,可以定义方法名相同,但是参数不同的方法。JNI中仅仅通过方法名是无法找到Java中对应的具体方法的,JNI将参数类型和返回值类型组合在一起作为方法签名 JNI方法签名格式: (参数签名格式...)返回值签名格式
4. 解析JNIEnv
JNIEnv是Native世界中Java环境的代表,通过JNIEnv* 指针可以在Native世界中方法Java世界的代码,它只在创建它的线程中有效,不能跨线程传递,不同线程的JNIEnv是彼此独立的
作用:
- 调用Java的方法
- 操作Java(操作Java中的变量和对象
libnativehelper/include/nativehelper/jni.h
JNIEnv的定义:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
使用预定义宏——cplusplus来区分C和C++,如果定义了_cplusplus,就是C++代码中的定义; JavaVM,它是虚拟机在JNI层的代表,在虚拟机进程中只有一个JavaVM,该进程的所有线程都可以使用这个javaVM。通过JavaVM的AttachCurrentThread函数可以获取到这个线程的JNIEnv,这样就可以在不同线程中调用Java方法。使用AttachCurrentThread函数的线程退出前,要调用DetachCurrentThread函数来释放资源
从C中JNIEnv的类型是JNINativeInterface* ,C++中JNIEnv的类型是_JNIEnv,查看_JNInv,是如何被定义的:
libnativehelper/include/nativehelper/jni.h
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jint GetVersion()
{ return functions->GetVersion(this);
......
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
jmethodID FromReflectedMethod(jobject method)
{ return functions->FromReflectedMethod(this, method); }
jfieldID FromReflectedField(jobject field)
{ return functions->FromReflectedField(this, field); }
jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic)
{ return functions->ToReflectedMethod(this, cls, methodID, isStatic); }
jclass GetSuperclass(jclass clazz)
{ return functions->GetSuperclass(this, clazz); }
....
jclass GetObjectClass(jobject obj)
{ return functions->GetObjectClass(this, obj); }
jboolean IsInstanceOf(jobject obj, jclass clazz)
{ return functions->IsInstanceOf(this, obj, clazz); }
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
{ return functions->GetMethodID(this, clazz, name, sig); }
_JNIEnv是一个结构体,内部包含了JNINativeInterface。同时在_JNIEnv中定义了很多的函数,这些都是用来获取Java中的一些数据
不管_JNIEnv都是跟JNINativeInterface有关的
libnativehelper/include/nativehelper/jni.h
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*);
...
上述结构定义很多和JNIEnv结构体对应的函数指针,通过函数指针的定义,能够定义位到虚拟机中的JNI寒暑表,从而时间JNI层在虚拟机中的函数调用,这样JNI层就可以调用Java世界的方法
5. 引用类型
JNI的引用类型,分别分为本地引用(Local References)、全局引用(Global References)和弱全局引用(Weak Glabal References)
5.1 本地引用
JNIEnv提供的函数所返回的引用基本上都是本地引用,本地引用的特点:
- 当Native函数返回时,这个本地引用就会被自动释放
- 在创建他的线程有效,不能够跨进程使用
- 局部引用是JVM负责的引用类型,受JVM管理
android_media_MediaRecorder_native_init(JNIEnv *env)
{
jclass clazz;
clazz = env->FindClass("android/media/MediaRecorder");
if (clazz == NULL) {
return;
}
...
}
这个clazz就是本地引用,他会在android_media_MediaRecorder_native_init函数调用返回后被自动释放。 当然也可以使用JNIEnv的DeleteLocalRef函数来手动删除本地引用
5.2 全局引用
- 在native函数返回时不会被自动释放,因此全局引用需要手动来进行释放,并且不会被GC回收
- 全局引用是可以跨线程使用的
- 全局引用不受JVM管理
JNIEnv的NewGloblaRef函数用来创建全局引用,调用JNIEnv的DeleteGlobalRef函数来释放全局引用
JNIMediaRecorderListener::JNIMediaRecorderListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
{
jclass clazz = env->GetObjectClass(thiz);
// 创建全局引用
mClass = (jclass)env->NewGlobalRef(clazz);
mObject = env->NewGlobalRef(weak_thiz);
}
JNIMediaRecorderListener::~JNIMediaRecorderListener()
{
// 释放全局引用
JNIEnv *env = AndroidRuntime::getJNIEnv();
env->DeleteGlobalRef(mObject);
env->DeleteGlobalRef(mClass);
}
5.2 弱引用
弱全局引用是特殊的全局引用,不同的是弱全局是可以被GC回收的,弱全局引用被GC回收之后会指向NULL。JNIEnv的NewWeakGlobalRef函数用来创建弱全局引用,调用JNIEnv的DeleteWeakGlobalRef函数来释放弱全局引用
判断弱全局引用是否被GC回收的方法:
if(env->IsSameObject (weakGlobalRef, NULL){
return false;
}
后记
- 参考刘望舒《Android进阶解密》