JNI相关

2016-12-05  本文已影响0人  小山包

参考资料: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函数的参数对应规则

4.native方法的注册方式

android.media.MediaScanner.native_init===>android_media_MediaScanner_native_init
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之分

jstring string = env->NewString(JNIEnv *env, const jchar*unicodeChars, jsize len);
jstring string = env->NewStringUTF(JNIEnv *env, const jchar*unicodeChars, jsize len);
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随时可能会被回收。
有三种引用类型:

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());
}
MyMediaScannerClient(JNIEnv *env, jobject client)
       :mEnv(env),
        mClient(env->NewGlobalRef(client)) //调用NewGlobalRef创建一个GlobalReference,这样mClient就不用担心被回收了。
{......}
//析构函数
virtual ~MyMediaScannerClient()
{
    mEnv->DeleteGlobalRef(mClient);//调用DeleteGlobalRef释放这个全局引用。
 }

9.异常处理

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

上一篇 下一篇

猜你喜欢

热点阅读