JNI相关

参考资料: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.异常处理

原文讲的很简单,不来处理了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容