天天看點

Android JNI程式設計規範

Demo

//增加學生資訊:
    addStu(JNIEnv *env,jobject valObj){
        //擷取jclass對象
        jclass cls=env->GetObjectClass(valObj);
        //GetFieldID方法用到取到jclas中的name字段。參數清單(jclass對象,字段名稱,字段類型) 這點類似于java的反射
        jstring name =(jstring)env->GetObjectField(valObj,env->GetFieldID(cls,"name","Ljava/lang/String;"));
        jint age=(jint)env->GetObjectField(valObj,env->GetFieldID(cls,"age","I"));
        jint sex =(jint)env->GetObjectField(valObj,env->GetFieldID(cls,"sex","I"));
        //建立一個結構類型的對象  jstringToString方法用于把jstring類型轉換成char *
        student stu={jstringToString(env,name),(int)age,(int)sex};
        //往向量的末尾增加一個對象
        stus.push_back(stu);
    }
           
//修改學生資訊
    upStu(JNIEnv *env,jobject obj,jobject objValue){
        jclass cls=env->GetObjectClass(objValue);
        jstring name=(jstring)env->GetObjectField(objValue,env->GetFieldID(cls,"name","Ljava/lang/String;"));
        jint sex =(jint)env->GetObjectField(objValue,env->GetFieldID(cls,"sex","I"));
        jint age=(jint)env->GetObjectField(objValue,env->GetFieldID(cls,"age","I"));

        char * searchName =jstringToString(env,name);

        for(int i=;i<stus.size();i++){
            student stu=stus.at(i);
            if(strcmp(stu.name,searchName)==){
                stus.at(i).sex=(int)sex;
                stus.at(i).age=(int)age;
            }
        }
    }
           
//查詢學生
    getStu(JNIEnv *env,jobject obj,jstring str){
        const char *nameStr=env->GetStringUTFChars(str,);
        jclass objectClass =(env)->FindClass("com/myjni/activity/Student");
        jfieldID name =env->GetFieldID(objectClass,"name","Ljava/lang/String;");
        jfieldID sex =env->GetFieldID(objectClass,"sex","I");
        jfieldID age =env->GetFieldID(objectClass,"age","I");

        for(int i=;i<stus.size();i++){
            student stu=stus.at(i);
            if(strcmp(stu.name,nameStr)==){
                env->SetObjectField(obj,env->NewStringUTF(stus.at(i).name));
                env->SetIntField(obj,sex,stus.at(i).sex);
                env->SetIntField(obj,age,stus.at(i).age);
            }
        }
        return obj;
    }
           
//擷取所有學生資訊
    getStus(JNIEnv *env,jobject obj){
        class objClass =(env)->FindClass("java/lang/Object");
        jobjectArray args = ;
        jsize len=stus.size();
        args=env->NewObjectArray(len,objClass,);
        jclass objectClass =(env)->FindClass("com/myjni/activity/Student");
        jfieldID name=(env)->GetFieldID(objectClass,"name","Ljava/lang/String;");
        jfieldID age=(env)->GetFieldID(objClass,"age","I");
        jfieldID sex =(env)->GetFieldID(objClass,"sex","I");

        for(int i=;i<len;i++){
            jobject tempObj =env->AllocObject(env->GetObjectClass(obj));
            student stu=stus.at(i);
            env->SetObjectField(tempObj,name,env->NewStringUTF(stus.at(i).name));
            env->SetObjectField(tempObj,age,stu.age);
            env->SetObjectField(tempObj,sex,stu.sex);
            env->SetObjectArrayElement(args,i,tempObj);
        }
        return args;  //傳回的是數組對象
    }
           

類型簽名:

Z   boolean 
B   byte
C   char
S   short
I   int
J   long 
F   float
D   double
Ljava/lang/String;   String
[I  int[]
           

資料類型

基本資料類型:
            boolean  byte  char  short  int  long  float  double  --Java
            jboolean jbyte jchar jshort jint jlong jfloat jdouble --jni
        引用資料類型:
            String    Array[]    Object  --Java

            String :
                jstring是JNI對應于String的類型,但是和基本類型不同的是,jstring不能直接當做C++的string用。否則編譯器會扔給你一個錯誤資訊。

                const char *str;
                str =env->GetStringUTFChars(prompt,false);   //先将java中的String轉換成chars
                if(str==null){
                    return null;
                }
                cout<<str<<endl;  //可以進行字元數組的輸出
                env->ReleaseStringUTFChars(prompt,str);   //注意釋放存儲,避免記憶體洩露

                //如果要轉換成string對象 
                char *tmpstr="returnStringSucceeded";
                jstring str =env->NewStringUTF(tmpstr);   //将char轉換成jstring類型

                    總結:
                        将java中的String類型轉換成JNI中de jstring類型

                        const char *chars=env->GetStringUTFChars(prompt,false);
                        jstring str =env->NewStringUTF(chars);


            數組類型:
                JNI為Java基本類型的數組提供了j*Array類型,比如int[] 對應的就是jintArray

                JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env,jobject obj,jintArray arr){
                    jint *carr;
                    carr =env->GetIntArrayElements(arr,false);
                    if(carr==NULL){
                        return 0;
                    }
                    jintsum =0;
                    for(int i=0;i<10;i++){
                        sum+=carr;
                    }
                    env->ReleaseIntArrayElements(arr,carr,0);
                    return sum;
                }

                    總結:
                        GetIntArrayElements和ReleaseIntArrayElements函數就死JNI提供用于處理int數組的函數。

            二維數組和String數組:
                在JNI中,二維數組和String數組都被視為object數組,因為數組和String被視為object。

                JNIEXPORT jobjectArray JNICALL Java_ObjectArrayTest_initInt2DArray(JNIEnv *env,jclass cls,int size){
                    //因為要傳回值,是以需要建立一個jobjectArray對象
                    jobjectArray result;
                    //建立一個jclass的引用
                    jclass intArrCls = env->FindClass("[I");
                    //為result配置設定空間
                    result =env->NewObjectArray(size,intArrCls,NULL);
                    for(int i=0;i<size;i++){
                        jint tmp[256];  //保證存儲空間足夠大
                        //為一維int數組iarr配置設定空間
                        jintArray iarr=env->NewIntArray(size);
                        for(int j=0;j<size;j++){
                            tmp[j]=i+j;
                        }
                        //為iarr指派
                        env->SetIntArrayRegion(iarr,0,size,tmp);
                        //為result的第i個元素指派
                        env->StObjectArrayElements(result,i,iarr);
                        env->DeleteLocalRef(iarr);
                    }
                    return result;
                }
                //建立一個二維int數組,并指派完畢

            注意:
                以上使用的函數調用方式都是針對C++的,如果要用在C中,所有的env->都要被替換成(*env)->,而且後面的函數中需要增加一個參數env
           

通路Java類的域和方法 通路Java類的私有域和方法

public class ClassA{
            String str="abcde";
            int number;
            public native void nativeMethod();
            private void javaMethod(){
                System.out.println("call javaMethod successed");
            }
            static{
                System.loadLibrary("ClassA");
            }
        }

        //C++實作
        JNIEXPORT void JNICALL Java_testclass_ClassCallDLL_nariveMethod(JNIEnv *env,jobject obj){
            //accessfield
            jclass cls =env->GetObjectClass(obj);  //得到對象

            jfieldID fid=env->GetFieldID(cls,"str","Ljava/lang/String;");
            jstring jstr =(jstring)env->GetObjectField(obj,fid);  
            const char *str=env->GetStringUTFChars(jstr,false);
            if(std::string(str)=="abcde")
                std::cout<<"accessfieldsuccesses"<<std::endl;
            //得到字元字段

            jint i=;
            fid =env->GetFieldID(cls,"number","I");
            env->SetIntField(obj,fid,i);
            //設定值

            jmethodID mid=env->GetMethodID(cls,"javaMethod","()V");
            env->CallVoidMethod(obj,mid);
            //調用不帶參數的方法
        }
           

在native方法中使用使用者定義的類

使用自定義的類和使用Java的基礎類(比如String)沒有太大的差別,關鍵的一點是,如果要使用自定義類,首先要能通路類的構造函數。

jclass cls=env->FindClass("Ltestclass/ClassB");   //建立一個自定義類的引用
        jmethodID mid=env->GetMethodID(cls,"<init>","(D)V");  //獲得這個類的構造函數

        jdouble dd=;
        jvalue args[];
        args[].d=dd;
        jobject obj=env->NewObjectA(cls,mid,args);  //生成了一個ClassB的對象,args是ClassB的構造函數的參數,它是一個jvalue*類型。 
           

異常處理

在native方法中發生了異常,傳導到Java中

jclass Cls;
env->ExceptionDescribe();
env->ExceptionClear();
errCls=env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(errCls,"thrownfromC++code");
//如果要抛出其他類型的異常,替換掉FindClass的參數即可。這樣,在Java中就可以接收native方法中抛出的異常。
           

類的相關操作

jclass FindClass(JNIEnv *env,const char *name); //查找類

常見異常:
    ClassFormatError  類的資料格式無效
    ClassCircularityError  該類或接口是自身的超類或超接口
    NoClassDefFoundError   沒有找到指定名稱的類或接口
    OOM  記憶體不足錯誤,即OutOfMemoryError
           
jclass GetSuperclass(JNIEnv *env,jclass clazz);  //擷取父類或者說超類
            //第二個參數傳入的是子類,否則傳回将是NULL
           
jboolean IsAssignableFrom(JNIEnv *env,jclass clazz,jclass clazz2);  //判斷class1對象能否安全的強制轉換為class2對象
            /*
                以下情況傳回true:   JNI_TRUE
                    、這兩個類參數引用同一個Java類
                    、第一個類是第二個類的子類
                    、第二個類是第一個類的某個接口
           
jclass GetObjectClass(JNIEnv *env,jobject obj);  //通過對象擷取這個類
            //對象不能為NULL,否則擷取的class肯定傳回也為NULL
           
jboolean isInstanceOf(JNIEnv *env,jobject obj,jclass clazz);  //判斷對象是否為某一個類的執行個體

            /*
                注意: 
                    傳回值可能産生異議,就是如果傳入的第二個參數為NULL對象,NULL對象可以強制轉換為各種類,所有這種情況也将會傳回JNI_TRUE,是以一定判斷傳入的對象是否為空。
            */
           
jboolean IsSameObject(JNIEnv *env,jobject ref1,jobject ref2);  //判斷兩個對象是否引用同一個類
            /*
                如果兩個對象均為空,傳回的值也會是JNI_TRUE是以使用時判斷對象為空
            */
           

調用Java方法:

jmethodID GetMethodID(JNIEnv *env,jclass clazz,const char *name,const char *sig);  //擷取一個Java方法的ID
        //這個函數将傳回非靜态類或接口執行個體方法的方法ID
/*
執行GetMethodID()函數将導緻未初始化的類初始化,如果要獲得構造函數的方法ID,使用<init>作為方法名,同時将void(V)作為傳回類型,如果找不到指定的ID将傳回NULL,同時異常可能有:
                    NoSuchMethodError 找不到指定的Java方法。
                    ExceptionInInitializerError 如果由于異常而導緻類初始化程式失敗
                    OutOfMemoryError 記憶體不足
*/
           
NativeType CallXXXMethod(JNIEnv *env,jobject obj,jmethodID methodID,va_list args); //調用XXX類型的Java方法
/*
執行Java類中的某個方法,需要注意的是這個裡的java類是非靜态的,由于Java的方法的類型比較多,是以該函數可能有以下幾種形式,如:CallObjectMethod,CallBooleanMethod,CallByteMetod,CallCharMethod,CallShortMethod,CallIntMethod和CallVoidMethod,需要注意的是,該函數的第三個參數為通過GetMethodID函數擷取的方法ID,最後一個參數為這個方法的參數表,最後的va_list宏可以通過搜尋擷取具體的使用方法。
*/
           

上面的三個均為非靜态類的擷取,執行調用,需要執行個體化這個類才可以執行,下面的為靜态調用。

jmethodID GetStatic MethodID(JNIEnv *env,jclass clazz,const char *name,const char *sig);

NativeType CallStatic XXXMethod(JNIEnv *env,jclass clazz,jmethodID methodID,...);
           

通路Java對象的域

jfieldID GetFieldID(JNIEnv *env,jclass clazz,const char *name,const char *sig);   //擷取執行個體對象的域ID
            /*
                非靜态的執行個體化後的對象,可能産生的異常有:
                    NoSuchFieldError 找不到指定的域
                    ExceptionInInitializerError 因為異常而導緻類初始化失敗
                    OutOfMemoryError記憶體不足
            */
           
NativeType GetXXXField(JNIEnv *env, jobject obj,jfieldID fieldID);
            //類似GetXXXMethod函數,可能有的類型有 GetObjectField,GetBooleanField,GetByteField,GetCharField,GetShortField,GetIntField,GetLongField
           
void SetXXXField(JNIEnv *env, jobject obj, jfieldID fieldID,NativeType value);   
            //Java的域可以指派的,可能有的類型有 SetObjectField,SetBooleanField,SetByteField,SetCharField,SetShortField,SetIntField,SetLongField
           

上面3種情況均為非靜态對象的域,對于不需要執行個體化對象的域,可以直接使用下面的

jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig);

        jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig);

        void SetStaticXXXField(JNIEnv *env, jclass clazz,jfieldID fieldID, NativeType value);