[置顶] Android JNI编程指南

1 JNI注册:
   c语言部分:
   int register_android_Test(JNIEnv* env)
   {
          return jniRegisterNativeMethods(env, "XXX/XXX/XXX", sMethod, NELEM(sMethod));
   }  
   extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
   {
        JNIEnv* env = NULL;
        jint result = -1;
     
        if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
        {
                return result;
        }
        register_android_Test(env);
    
        return JNI_VERSION_1_4;
   }
    JAVA部分代码
    static
    {
         try
         {
               System.loadLibrary("XXX");          
         }
         catch (UnsatisfiedLinkError ule)
         {
          
                     Log.e("Test JNI", "WARNING: Could not load JNI :" + ule.toString());
           
          }
    }
   在JAVA 中调用System.loadLibrary,系统会调用JNI库中定义的JNI_OnLoad,有JNI_OnLoad完成JNI函数的注册。
   System.loadLibrary函数的参数是JNI库的文件名,但是不包括前面的lib和扩展名,例如我们的JNI库的文件libabcd.so,那么就要调用System.loadLibrary("abcd");
   JNI库通常保存在目标机的system/lib下或者放在eclips工程下的libs\armeabi目录中。
   jniRegisterNativeMethods完成JNI函数在JAVA中的映射,JNI函数是通过一个JNINativeMethod类型的数组来描述的。
   jniRegisterNativeMethods函数的第二个参数是要映射的JAVA类的包名+类名。
   例如定义了下面的类
   package com.android;
   public class T1  
   {
    .......
   }
   那么第二个参数就是"com/android/T1" ,这个名字不能搞错了,否者JAVA调用loadLibrary会报异常的。
    
2   JAVA类中访问JNI函数。
    Andorid使用了JNINativeMethod描述了函数的参数和返回值,实现C到JAVA的映射
    JNINativeMethod结构描述如下:
    typedef struct {
           const char* name;
           const char* signature;
           void* fnPtr;
    } JNINativeMethod;
    第一个变量name是Java中函数的名字。
    第二个变量signature,用字符串是描述了函数的参数和返回值
    第三个变量fnPtr是函数指针,指向C函数。
 
 其中比较难以理解的是第二个参数,例如
 "()V"
 "(II)V"
 "(Ljava/lang/String;Ljava/lang/String;)V"
 实际上这些字符是与函数的参数类型和返回值一一对应的。
    "()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
 "(II)V" 表示 void Func(int, int);
  具体的每一个字符的对应关系如下
       字符    Java类型      C类型
          V        void               void
          Z       jboolean        boolean
           I        jint                  int
          J       jlong               long
          D       jdouble          double
          F       jfloat                float
          B       jbyte                byte
          C       jchar               char
          S       jshort              short

 数组则以"["开始,用两个字符表示
         字符     Java类型            C类型
             [I       jintArray                int[]
             [F       jfloatArray           float[]
            [B       jbyteArray           byte[]
             [C       jcharArray         char[]
             [S       jshortArray        short[]
            [D       jdoubleArray     double[]
            [J       jlongArray           long[]
            [Z       jbooleanArray   boolean[]

    上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾中间是用"/" 隔开的包及类名。
    而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring

 Ljava/lang/String; String 类
 Ljava/net/Socket; Socket 类

 如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。

 例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"
 FileStatus就是在FileUtils类的内部定义的一个类

    下面举个例子
    在C代码中
    static jint android_init(JNIEnv* env, jobject obj)
   {
              return 0;
    }
   static jboolean android_close(JNIEnv* env, jobject obj)
   {
            return true;
   }
   static jboolean android_Test(JNIEnv* env, jobject obj,jbyte para1,jbyte para2 , jbyte para3 )
  {
             return true;
  }
    static JNINativeMethod sMethod[] =
  {
              /* name,       signature, funcPtr */
             {"native_init", "()I", (void*)android_init},
             {"native_close", "()Z", (void*)android_close},
            {"native_Test", "(BBB)Z", (void*)android_Test},
    };
    int register_android_Test(JNIEnv* env)
    {
              return jniRegisterNativeMethods(env, "com/android/test/mytest", sMethod, NELEM(sMethod));
     }
     register_android_Test函数在 JNI_OnLoad里面调用。
 
 JAVA代码中
 package com.android.test;
 public class mytest      
 {
      static {
         try { 
             System.loadLibrary("test_jni"); //加载JNI库“libtest_jni.so”,这个库就是上面C代码编译出来的库
         }
         catch (UnsatisfiedLinkError ule) {
             Log.e("JNI", "WARNING: Could not load libtest_jni.so :" + ule.toString());
         }
     }
     public  native int native_init( ); //注意JNI函数一定要加上native修饰
     public  native boolean native_close( );
     public  native boolean native_Test(byte group,byte bit , byte data );
    }
    在JAVA代码中要使用这些JNI函数,就直接new一个类调用就可以了,代码如下:
    mytest myjniclass =new mytest;
    myjniclass.native_init();
    myjniclass.native_Test(1,2,3);
    myjniclass.native_close();
   
3   JNI中回调JAVA函数
    3.1 JNI中回调JAVA类的静态函数
    例如 前面的JAVA类中定义了一个静态函数
    public static void TestStaitc(   )
    {
     
    }
    在C代码中首先定义jmethodID和jclass类型的全局变量
    static jclass gClazz;
    static jmethodID method_TestStaitc;
    在register_android_Test函数中增加下面的代码
    jclass clazz = env->FindClass("com/android/test/mytest");
    gClazz = jclass(env->NewGlobalRef(clazz));
     method_TestStaitc = env->GetStaticMethodID(clazz,"TestStaitc","()V");
 
 在回调的地方
 env->CallStaticVoidMethod(gClazz,method_TestStaitc);
 就大功告成了!
 
 当然了上面对gClazz和method_TestStaitc初始化的代码也可以放在回调的地方调用,但是因为JAVA函数可能会多次调用,放在注册的时候调用可以减少系统的开销。
 env->FindClass("com/android/test/mytest");返回的是一个局部引用,局部引用在本地方法返回时会自动释放,所以我们要调动gClazz = jclass(env->NewGlobalRef(clazz));创建一个全局引用,这个引用不会自动释放,我们在不需要这个引用的时候要显示调用env->DeleteGlobalRef(gClazz)来释放。
 (注:除了FindClass,还有下面的函数也可以获取类的引用
       jclass GetObjectClass(jobject obj)   根据一个对象,获取该对象的类
              jclass GetSuperClass(jclass obj)     获取一个类的父类)

 GetStaticMethodID 是返回静态方法的jmethodID,注意这个函数的第一参数一定是类的引用(jclass ),而不是对象的引用。第二个参数是JAVA中定义的函数名,第三个参数是JAVA函数的参数和返回值
        同样道理CallStaticVoidMethod的第一个参数也必须是jclass类型,否者会报异常。
    CallStaticVoidMethod 是调用无返回值的静态方法,JNI中还有许多其他返回类型的方法,如下:
    CallStaticObjectMethod //返回一个JAVA的对象
    CallStaticBooleanMethod
    CallStaticByteMethod
    CallStaticCharMethod
    CallStaticShortMethod
    CallStaticIntMethod
    CallStaticLongMethod
    CallStaticFloatMethod
    CallStaticDoubleMethod
    CallStaticVoidMethod
   
    例如:如果JAVA函数原型为public static int TestStaitc(  int para );
    那么就调用
    jint ret = env->CallStaticIntMethod(gClazz,method_TestStaitc,1);
    这里注意一个参数一定要是类的引用,而不是对象的引用。
   
    3.2 JNI中回调JAVA类的非静态函数
    例如 前面的JAVA类中定义了一个静态函数
    public   void Test(   )
    {
     
    }
    在C代码中首先定义jmethodID 的全局变量
   
    static jmethodID method_Test ;
    在register_android_Test函数中增加下面的代码
    jclass clazz = env->FindClass("com/android/test/mytest");
    method_Test = env->GetMethodID(clazz,"Test","()V");
 
 在回调的地方
 env->CallVoidMethod(obj,method_Test);
 就大功告成了!
 这里要注意,obj的对象的引用(jobject),而不是类的引用(jclass);
 jobject是通过JNI函数的第二个参数传递进来的,如果要创建一个全局的对象,可以调用gObject = env->NewGlobalRef(obj);
 同样的除了CallVoidMethod ,JNI中还有许多其他返回类型的方法
 CallObjectMethod //返回一个JAVA的对象
    CallBooleanMethod
    CallByteMethod
    CallCharMethod
    CallShortMethod
    CallIntMethod
    CallLongMethod
    CallFloatMethod
    CallDoubleMethod
    CallVoidMethod
    3.3 JNI调用Java方法不在一个类中。
    前面介绍的都是JNI和JAVA在同一个类中,如果JNI和JAVA不在一个类中,如何访问呢?
    例如我们要访问 adc/def包里面的test类的函数,的 void test(void) 函数 可以用下面的代码
    jclass clazz = env->FindClass("adc/def/test");
    jmethodID method1 = env->GetMethodID(clazz,"test","()V");
    jobject obj= env->AllocObject(clazz);//这里创建一个对象实例,如果是静态函数,就不需要创建对象的示例了
    env->CallVoidMethod(obj,method1); //调用函数,如果是静态函数,就应该是env->CallStaticVoidMethod(clazz,method1);前面的method1 = env->GetStaticMethodID(clazz,"test","()V");
    AllocObject 仅仅是分配了一个test的对象,但是不会调用这个类的构造函数,那么对于需要构造函数的类该怎么处理呢?
    首先需要获得构造函数的jmethodID
    jmethodID methodInit = env->GetMethodID(clazz,"<init>","()V"); 注意这里的第二个参数不是构造函数的名字,而是<init>
    然后创建对象
    jobject objtest = env->NewObject( clazz,methodInit);
    如果构造函数需要参数,就按参数的顺序依次加载methodInit的后面,就好了。
    注意AllocObject和NewObject创建的都是局部引用,在方法结束后可能会被回收。如果要在其他的方法中使用,需要创建全局引用gObject = env->NewGlobalRef(objtest);
    对于全局引用,在不需要再使用的时候,要显示的调用env->DeleteGlobalRef(gObject);
    对于局部引用,可以不用显示的释放。也可以在函数结束时调用env->DeleteLocalRef(Object);释放
 
4   JNI中访问JAVA的变量
    对于JNI中访问JAVA变量的方法和函数类似。
    首先需要定义一个jfieldID。
    jfieldID   field;
    JNI提供了两个函数 GetFieldID和GetStaticMethodID分别获取非静态变量和静态变量的jfieldID
    然后用上面两个函数初始化的jfieldID变量。
   
    获取或者设置这些变量的值可以用下面的函数
    void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
    void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value)
    void SetByteField(jobject obj, jfieldID fieldID, jbyte value)
    void SetCharField(jobject obj, jfieldID fieldID, jchar value)
    void SetShortField(jobject obj, jfieldID fieldID, jshort value)
    void SetIntField(jobject obj, jfieldID fieldID, jint value)
    void SetLongField(jobject obj, jfieldID fieldID, jlong value)
    void SetFloatField(jobject obj, jfieldID fieldID, jfloat value)
    void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)
   
    void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
    void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value)
    void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value)
    void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value)
    void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value)
    void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
    void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value)
    void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value)
    void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value)
   
    jobject GetObjectField(jobject obj, jfieldID fieldID)
    jboolean GetBooleanField(jobject obj, jfieldID fieldID)
    jbyte GetByteField(jobject obj, jfieldID fieldID)
    jchar GetCharField(jobject obj, jfieldID fieldID)
    jshort GetShortField(jobject obj, jfieldID fieldID)
    jint GetIntField(jobject obj, jfieldID fieldID)
    jlong GetLongField(jobject obj, jfieldID fieldID)
    jfloat GetFloatField(jobject obj, jfieldID fieldID)
    jdouble GetDoubleField(jobject obj, jfieldID fieldID)
   
    jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
    jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID)
    jbyte GetStaticByteField(jclass clazz, jfieldID fieldID)
    jchar GetStaticCharField(jclass clazz, jfieldID fieldID)
    jshort GetStaticShortField(jclass clazz, jfieldID fieldID)
    jint GetStaticIntField(jclass clazz, jfieldID fieldID)
    jlong GetStaticLongField(jclass clazz, jfieldID fieldID)
    jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID)
    jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID)
   
    下面举例说明如何使用,例如前面的mytest类里面定义了两个变量
    int i;
    String Message;
    JNI C代码
    定义两个全局变量
    jfieldID mStringMessageId;
    jfieldID nIntiId;
    在register_android_Test函数中增加下面的代码
    jclass clazz = env->FindClass("com/android/test/mytest");
    nIntiId = env->GetFieldID(clazz, "i", "I");
 mStringMessageId = env->GetFieldID(clazz, "Message", "Ljava/lang/String;");
 
 获取变量的值
 int i = GetIntField(obj,nIntiId);
 jstring Messagestr = jstring(env->GetObjectField(obj, mStringMessageId));
 设置变量
 GetIntField(obj,nIntiId,5);
 char tempbuf[10];
 sprintf(tempbuf,"test" );
 env->SetObjectField(obj, mStringMessageId, env->NewStringUTF(tempbuf));
 对于访问JNI和JAVA不在同一个类的变量的方法,和3.3介绍的类似,这里就不详细描述了。
 
5 JNI参数的传递
  5.1 JNI类型描述
    Java类型        JNI本地类型        描述 
    boolean         jboolean           C/C++8位整型
    byte                jbyte                   C/C++带符号的8位整型
    char                jchar                 C/C++无符号的16位整型
    short               jshort               C/C++带符号的16位整型
    int                    jint                   C/C++带符号的32位整型
    long                jlong                C/C++带符号的64位整型e
    float                 jfloat             C/C++32位浮点型
    double           jdouble         C/C++64位浮点型
    Object            jobject            任何Java对象,或者没有对应java类型的对象
    Class             jclass             Class对象
    String             jstring            字符串对象
    Object[]          jobjectArray       任何对象的数组
    boolean[]       jbooleanArray      布尔型数组
    byte[]              jbyteArray         带符号的8位整型数组
    char[]             jcharArray         字符型数组
    short[]           jshortArray        短整型数组
    int[]                jintArray          整型数组
    long[]             jlongArray         长整型数组
    float[]             jfloatArray        浮点型数组
    double[]        jdoubleArray       双浮点型数组
    对于简单数据类型例如byte char int boolean等等只来的,直接使用就可以了
    
    5.2数组的使用:
    为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数,XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
    我们可以在C语言中操作这些指针了。操作完成功后ReleaseXXXArrayElements
    函数                                              Java 数组类型       本地类型 
 GetBooleanArrayElements        jbooleanArray       jboolean
 GetByteArrayElements               jbyteArray                 jbyte
 GetCharArrayElements           jcharArray                 jchar
 GetShortArrayElements          jshortArray         jshort
 GetIntArrayElements                jintArray           jint
 GetLongArrayElements           jlongArray          jlong
 GetFloatArrayElements          jfloatArray         jfloat
 GetDoubleArrayElements         jdoubleArray        jdouble
 
 ReleaseBooleanArrayElements      jbooleanArray       jboolean
 ReleaseByteArrayElements         jbyteArray          jbyte
 ReleaseCharArrayElements         jcharArray          jchar
 ReleaseShortArrayElements        jshortArray         jshort
 ReleaseIntArrayElements          jintArray           jint
 ReleaseLongArrayElements         jlongArray          jlong
 ReleaseFloatArrayElements        jfloatArray         jfloat
 ReleaseDoubleArrayElements       jdoubleArray        jdouble
 具体可以看下面的例子
 static jint Test(JNIEnv* env, jobject obj ,  jintArray buf )
 {
  jint* ptr= env->GetIntArrayElements(buf, 0 );
  jsize  len = env->GetArrayLength(buf);
  int i;
  for(i=0;i<len;i++)
  {
   ALOGD("buf[%d]=%d",i,ptr[i] );
   ptr[i]=9;
  }
  env->ReleaseIntArrayElements(buf, ptr, 0 );
  return 0;
 }
 执行完这个JNI函数后,JAVA传进来的数组的值就全部变为9了。
 上面看到的是数组座位参数传递进来,那么如何返回一个数组呢?
 我们可以调用NewXXXArray 创建一个数组,这个函数的参数就是要生成的数组的长度。然后return这个数组就可以了。如果需要给数组赋值,参考上面的代码GetIntArrayElements就可以了
 下面是例子:
    jintArray newbuf = env->NewIntArray(5);
    ptr= env->GetIntArrayElements(newbuf, 0 );
    for(i=0;i<5;i++)
   {
  
          ptr[i]=6;
    }
 env->ReleaseIntArrayElements(newbuf, ptr, 0 );
 return newbuf;
 对于其他数据类型的数组,调用相应的NewXXXArray就好了,把XXX换为相应的数据类型。
 
 5.3对象的使用
 通过使用之前介绍的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。
        FindClass
     GetMethodID
     AllocObject
        CallXXXMethod (XXX为相应的数据类型)
        CallStaticXXXMethod(XXX为相应的数据类型)
        GetStaticMethodID
        NewObject
     GetFieldID
     GetStaticFieldID
     GetXXXField(XXX为相应的数据类型)
     SetXXXField(XXX为相应的数据类型)
   
 5.4 String类的使用
 其实String(jstring)也是数以一种对象类型,不过由于使用的频率非常高,所以JNI专门提供了一系列的函数进行操作。
 在JAVA中String类都是16为的unicode,而C\C++中都是8位的char型,两者之间如何转换呢?
 String到char型的转换:
 const char *ptr = env->GetStringUTFChars(stringname, NULL);
 jsize len =  GetStringUTFLength(stringname)  ;
 这里就可以对ptr进行处理了,注意,ptr的内容不能改变。
 处理完调用env->ReleaseStringUTFChars(stringname, ptr);
 char型到String类型的转换
 jstring value = NULL;
 value = env->NewStringUTF(buf); //buf是char型的指针
 
6   JNI在线程中访问JAVA的函数和变量
    JNIEnv 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ; 而线程和应用的主线程不在同一个线程中,那么其他的线程中如何访问JAVA的函数和类呢?
    首先我们要在创建线程的JNI函数中初始化两个全局变量
    static jobject     gObject;
 static JavaVM      *gs_jvm;
    gObject = env->NewGlobalRef(obj);
    env->GetJavaVM(&gs_jvm);
    在线程的开始处
    JNIEnv *env1=NULL ;
    gs_jvm->AttachCurrentThread(&env1, NULL);
    之后就可以参考前面的方法访问JAVA的函数和变量了
    在退出线程的地方调用
    gs_jvm->DetachCurrentThread();
    下面是示例代码:
    static jobject     gObject=NULL;
 static JavaVM      *gs_jvm=NULL;
 static void * Test_thread( void*  arg )
 {
  JNIEnv *env1=NULL ;
  gs_jvm->AttachCurrentThread(&env1, NULL);
  while(1)
  {
  //访问JAVA中的函数和变量
  }
  gs_jvm->DetachCurrentThread();
 }
 static jint StartInterrupt(JNIEnv* env, jobject obj  )
 {
  pthread_t  threadId;
  if(gObject==NULL)
  {
   gObject = env->NewGlobalRef(obj);    
  }
  if(gs_jvm==NULL)
  {
   env->GetJavaVM(&gs_jvm);
  }
  pthread_create( &threadId, NULL, Test_thread, NULL );
 }
 
 

    

你可能感兴趣的:(编程,android,jni)