参考资料:http://blog.csdn.net/innost/article/details/47204557
1.加载JNI库
原则上是在调用native方法之前加载即可,习惯上写在使用native函数的类中:
static {
System.loadLibrary("xxx");//xxx为native库的名字
}
2.native库的名字
System.loadLibrary("xxx")
中的xxx是库的名字,在不同的平台会扩展为相应的动态库名字,Linux->libxxx.so,Windows->xxx.dll
3.Java静态方法以及实例方法与native函数的参数对应规则
- 静态方法对应到native函数后,第一个参数为JNIEnv *env,之后的参数为原方法的参数对应到native语言的类型
- 实例方法对应过来第一个参数也是JNIEnv *env,第二个参数是代表调用实例的 jobject thiz参数,之后的参数跟静态方法一样
4.native方法的注册方式
- 静态注册
通过函数名来确定哪个Java方法对应哪个native函数,如
android.media.MediaScanner.native_init===>android_media_MediaScanner_native_init
- 动态注册
JNI技术中有一个结构图来储存双方的对应关系
typedef struct {
//Java中native函数的名字,不用携带包的路径。例如“native_init“。
const char* name;
//Java函数的签名信息,用字符串表示,是参数类型和返回值类型的组合。
const char* signature;
//JNI层对应函数的函数指针,注意它是void*类型。
void* fnPtr;
} JNINativeMethod;
实际注册的时候,先定义一个JNINativeMethod数组,把要注册的Java方法都放进这个数组里,
static JNINativeMethod gMethods[] = {
......
{
"processFile" //Java中native函数的函数名。
//processFile的签名信息,签名信息的知识,后面再做介绍。
"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
(void*)android_media_MediaScanner_processFile //JNI层对应函数指针。
},
......
{
"native_init",
"()V",
(void *)android_media_MediaScanner_native_init
},
......
};
然后调用
AndroidRuntime::registerNativeMethods(env, "android/media/MediaScanner", gMethods, NELEM(gMethods));
这些事情一般在
jint JNI_OnLoad(JavaVM* vm, void* reserved)
中做。一个示例的写法:
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
//该函数的第一个参数类型为JavaVM,这可是虚拟机在JNI层的代表喔,每个Java进程只有一个
//这样的JavaVM
JNIEnv* env = NULL;
jintresult = -1;
if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
gotobail;
}
...... //动态注册MediaScanner的JNI函数。
if(register_android_media_MediaScanner(env) < 0) {
goto bail;
}
......
returnJNI_VERSION_1_4;//必须返回这个值,否则会报错。
}
其中register_android_media_MediaScanner调用了
AndroidRuntime::registerNativeMethods(env, "android/media/MediaScanner", gMethods, NELEM(gMethods));
5.数据类型对应
基本类型的转换
Java | Native类型 | 符号属性 | 字长 |
---|---|---|---|
boolean | jboolean | 无符号 | 8位 |
byte | jbyte | 无符号 | 8位 |
char | jchar | 无符号 | 16位 |
short | jshort | 有符号 | 16位 |
int | jint | 有符号 | 32位 |
long | jlong | 有符号 | 64位 |
float | jfloat | 有符号 | 32位 |
double | jdouble | 有符号 | 64位 |
引用数据类型的转换
Java引用类型 | Native类型 |
---|---|
All objects | jobject |
java.lang.Class实例 | jclass |
java.lang.String实例 | jstring |
java.lang.Throwable实例 | jthrowable |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
Object[] | jobjectArray |
long[] | jlongArray |
boolean[] | jbooleanArray |
float[] | floatArray |
byte[] | jbyteArray |
double[] | jdoubleArray |
6.调用自定义类型的对象的方法和字段
三步:
- 获取类型对象
jclass clazz = mEnv->FindClass("android/media/MediaScannerClient");
- 通过
jfieldID GetFieldID(jclass clazz,const char*name, const char *sig);
jmethodID GetMethodID(jclass clazz, const char*name,const char *sig);
这两个方法获取到目标对象的字段或方法id/句柄/引用whatever不管怎么叫,反正就是能找到这个字段或方法的一个东西
- 调用
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
mEnv->GetXXXField(JNIEnv *env, jobject obj, jfieldID fieldID)
其中XXX为native类型,具体如下:
getter | setter |
---|---|
GetObjectField() | SetObjectField() |
GetBooleanField() | SetBooleanField() |
GetByteField() | SetByteField() |
GetCharField() | SetCharField() |
GetShortField() | SetShortField() |
GetIntField() | SetIntField() |
GetLongField() | SetLongField() |
GetFloatField() | SetFloatField() |
GetDoubleField() | SetDoubleField() |
第一个参数为目标对象,第二个参数为methodid,后面的参数就是原方法的参数。
举个栗子:
//获取类型对象
jclass clazz = mEnv->FindClass("android/media/MediaScannerClient");
//取出MediaScannerClient类中函数scanFile的jMethodID。
mMethodID = mEnv->GetMethodID(mediaScannerClientInterface, "scanFile", "(Ljava/lang/String;JJ)V");
//调用目标对象的方法
mEnv->CallVoidMethod(mClient, mMethodID, pathStr,
lastModified, fileSize);
7.jstring相关
Java字符串统一为Unicode编码,所以转换成c++字符串的时候有utf和Unicode之分
- c++字符串->Java字符串
jstring string = env->NewString(JNIEnv *env, const jchar*unicodeChars, jsize len);
jstring string = env->NewStringUTF(JNIEnv *env, const jchar*unicodeChars, jsize len);
- Java字符串->c++字符串
//调用JNIEnv的GetStringUTFChars得到本地字符串pathStr
const char *pathStr = env->GetStringUTFChars(jstring string, jboolean *isCopy);
- 释放字符串空间
ReleaseStringChars
ReleaseStringUTFChars
7.JNI的签名
使用javap生成就行了,不用去死记硬背了
8.垃圾回收
例子:
static jobject save_thiz = NULL; //定义一个全局的jobject
static void android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz, jstring path, jstring mimeType, jobject client)
{
......
//保存Java层传入的jobject对象,代表MediaScanner对象
save_thiz = thiz;
......
return;
}
//假设在某个时间,有地方调用callMediaScanner函数
void callMediaScanner()
{
//在这个函数中操作save_thiz,会有问题吗?
}
单纯在c++里传递引用save_thiz = thiz;
是不会增加引用计数的,需要JNI专门的引用类型包装一下。在上面的代码中,save_thiz随时可能会被回收。
有三种引用类型:
- Local Reference:本地引用。在JNI层函数中使用的非全局引用对象都是Local Reference。它包括函数调用时传入的jobject、在JNI层函数中创建的jobject。LocalReference最大的特点就是,一旦JNI层函数返回,这些jobject就可能被垃圾回收。
virtualbool scanFile(const char* path, long long lastModified,
long long fileSize)
{
jstringpathStr;
//调用NewStringUTF创建一个jstring对象,它是Local Reference类型。
if((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
//调用Java的scanFile函数,把这个jstring传进去
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr,
lastModified, fileSize);
/*
根据LocalReference的说明,这个函数返回后,pathStr对象就会被回收。所以
下面这个DeleteLocalRef调用看起来是多余的,其实不然,这里解释一下原因:
1)如果不调用DeleteLocalRef,pathStr将在函数返回后被回收。
2)如果调用DeleteLocalRef的话,pathStr会立即被回收。这两者看起来没什么区别,
不过代码要是像下面这样的话,虚拟机的内存就会被很快被耗尽:
for(inti = 0; i < 100; i++)
{
jstring pathStr = mEnv->NewStringUTF(path);
......//做一些操作
//mEnv->DeleteLocalRef(pathStr); //不立即释放Local Reference
}
如果在上面代码的循环中不调用DeleteLocalRef的话,则会创建100个jstring,
那么内存的耗费就非常可观了!
*/
mEnv->DeleteLocalRef(pathStr);
return(!mEnv->ExceptionCheck());
}
- Global Reference:全局引用,这种对象如不主动释放,就永远不会被垃圾回收。要注意的就是要在适当的位置(比如析构函数中)调用 DeleteGlobalRef释放全局引用。
MyMediaScannerClient(JNIEnv *env, jobject client)
:mEnv(env),
mClient(env->NewGlobalRef(client)) //调用NewGlobalRef创建一个GlobalReference,这样mClient就不用担心被回收了。
{......}
//析构函数
virtual ~MyMediaScannerClient()
{
mEnv->DeleteGlobalRef(mClient);//调用DeleteGlobalRef释放这个全局引用。
}
- Weak Global Reference:弱全局引用,一种特殊的GlobalReference,在运行过程中可能会被垃圾回收。所以在程序中使用它之前,需要调用JNIEnv的IsSameObject判断它是不是被回收了。
9.异常处理
原文讲的很简单,不来处理了。