JNI使用手册,其一,实用篇,先用起来。

来战!

  • 简而言之,先抛几个例子上手,以后就算忘了,大概只要参考这几个例子就行了。
  • JNI手册系列,主要在Android平台上运行。

Java层和Native层的传递调用

  • 传递基本类型

    • 基本传递方式,Java层声明native方法,Native层实现;
    public class NativeTransferAdapter {
        public native int getIntData(int param);
    }
    
    extern "C"
    JNIEXPORT jint JNICALL
    Java_com_sslyxhz_ndkcourse_NativeTransferAdapter_getIntData(JNIEnv *env, jobject instance, jint param) {
        LOGI("getIntData, param:%d", param);
        jint result = param + 1;
        return result;
    }
    
  • 传递数组类型

    • 需要注意的是,在Native层创建的对象要记得删除;
    public class NativeTransferAdapter {
        public native int[][] getIntByteArraysData(int[][] param);
    }   
    
    extern "C"
    JNIEXPORT jobjectArray JNICALL
    Java_com_sslyxhz_ndkcourse_NativeTransferAdapter_getIntByteArraysData(JNIEnv *env, jobject instance, jobjectArray param) {
        jint rows = env->GetArrayLength(param);
        for(int i=0;i<rows; ++i) {
            jintArray rowArray = (jintArray) env->GetObjectArrayElement(param, i);
            int cols = env->GetArrayLength(rowArray);
            for(int j=0; j<cols; ++j){
                jint value = env->GetIntArrayElements(rowArray, 0)[j];
                LOGI("getIntByteArraysData, param : <%d, %d> = %d", i, j, value);
            }
        }
        
        int size = 3;
        jclass intArrayClass = env->FindClass("[I");
        jobjectArray result = env->NewObjectArray(size,intArrayClass, NULL);
        
        for(int i=0;i<size;i++){
            jintArray rowArray = env->NewIntArray(size);
            jint tempArray[size];
            for(int j=0;j<size;j++){
                tempArray[j]=i+j;
            }
            env->SetIntArrayRegion(rowArray,0,size,tempArray);
            env->SetObjectArrayElement(result,i,rowArray);
            env->DeleteLocalRef(rowArray);
        }
        env->DeleteLocalRef(intArrayClass);
        
        return result;
    }
    
  • 传递Set类型

    • 此处已经涉及到如何从Java层中定位到类,并调用类中的方法;
    public class NativeTransferAdapter {
        public native Set<String> getSetData(Set<String> param);
    }   
    
    extern "C"
    JNIEXPORT jobject JNICALL
    Java_com_sslyxhz_ndkcourse_NativeTransferAdapter_getSetData(JNIEnv *env, jobject instance, jobject param) {
        jclass hashSetClass = env->FindClass("java/util/HashSet");
        jclass iteratorClass = env->FindClass("java/util/Iterator");
        if(hashSetClass == NULL || iteratorClass == NULL){
            return NULL;
        }
        jmethodID init_set_mid = env->GetMethodID(hashSetClass, "<init>", "()V");
        jmethodID add_set_mid = env->GetMethodID(hashSetClass, "add", "(Ljava/lang/Object;)Z");
        jmethodID iterator_set_mid = env->GetMethodID(hashSetClass, "iterator", "()Ljava/util/Iterator;");
        
        jmethodID hasNext_iter_mid = env->GetMethodID(iteratorClass, "hasNext", "()Z");
        jmethodID next_iter_mid = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
        
        jobject iter_obj = env->CallObjectMethod(param, iterator_set_mid);
        while(env->CallBooleanMethod(iter_obj, hasNext_iter_mid)) {
            jstring jvalue = (jstring) env->CallObjectMethod(iter_obj, next_iter_mid);
            const char* cvalue = env->GetStringUTFChars(jvalue, 0);
            LOGI("getSetData, param item:%s", cvalue);
            env->DeleteLocalRef(jvalue);
        }
        
        jobject result = env->NewObject(hashSetClass, init_set_mid);
        int size = 3;
        for(int i=0;i<size;++i){
            jstring testName = env->NewStringUTF("TestNameFromC++");
            env->CallBooleanMethod(result, add_set_mid, testName);
            env->DeleteLocalRef(testName);
        }
        env->DeleteLocalRef(hashSetClass);
        env->DeleteLocalRef(iteratorClass);
        return result;
    }
    
  • 传递Map类型

    • 照本宣科,与传递Set非常相似
    public class NativeTransferAdapter {
        public native Map<String, String> getMapData(Map<String, String> param);
    }   
    
    extern "C"
    JNIEXPORT jobject JNICALL
    Java_com_sslyxhz_ndkcourse_NativeTransferAdapter_getMapData(JNIEnv *env, jobject instance, jobject param) {
        jclass hashMapClass = env->FindClass("java/util/HashMap");
        jclass setClass = env->FindClass("java/util/Set");
        jclass iteratorClass = env->FindClass("java/util/Iterator");
        if(hashMapClass == NULL || iteratorClass == NULL){
            return NULL;
        }
        
        jmethodID init_map_mid = env->GetMethodID(hashMapClass, "<init>", "()V");
        jmethodID put_map_mid = env->GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
        jmethodID get_map_mid = env->GetMethodID(hashMapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
        jmethodID keySet_map_mid = env->GetMethodID(hashMapClass, "keySet", "()Ljava/util/Set;");
        
        jmethodID iterator_set_mid = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
        
        jmethodID hasNext_iter_mid = env->GetMethodID(iteratorClass, "hasNext", "()Z");
        jmethodID next_iter_mid = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
        
        jobject jKeySet = env->CallObjectMethod(param, keySet_map_mid);
        jobject jIterarot = env->CallObjectMethod(jKeySet, iterator_set_mid);
        while(env->CallBooleanMethod(jIterarot, hasNext_iter_mid)) {
            jstring jHashMapKey = (jstring) env->CallObjectMethod(jIterarot, next_iter_mid);
            jstring jHashMapValue = (jstring) env->CallObjectMethod(param, get_map_mid, jHashMapKey);
        
            const char *strKey = env->GetStringUTFChars(jHashMapKey, JNI_FALSE);
            const char *strValue = env->GetStringUTFChars(jHashMapValue, JNI_FALSE);
        
            LOGI("getMapData, param <%s, %s>", strKey, strValue);
        
            env->DeleteLocalRef(jHashMapKey);
            env->DeleteLocalRef(jHashMapValue);
        }
        env->DeleteLocalRef(jKeySet);
        env->DeleteLocalRef(jIterarot);
        env->DeleteLocalRef(setClass);
        env->DeleteLocalRef(iteratorClass);
        
        jobject result = env->NewObject(hashMapClass, init_map_mid);
        for(int i=0;i<3;++i){
            jstring testKey = env->NewStringUTF("TestKey");
            jstring testValue = env->NewStringUTF("TestValue");
            env->CallObjectMethod(result, put_map_mid, testKey, testValue);
        
            env->DeleteLocalRef(testKey);
            env->DeleteLocalRef(testValue);
        }
        env->DeleteLocalRef(hashMapClass);
        
        return result;
    }
    
  • 指针传递

    • 通常将指针转成jlong进行传递
    • 暂不考虑128位CPU
  • 内存块传递

    • GetPrimitiveArrayCritical,访问Java底层数据
    • ByteBuffer,但操作接口较少;

动态注册

  • 动态注册优势;

    • 缩短方法名;
    • 提高建立映射关系时的效率;
  • 示例

public class NativeRegisterAdapter {
    public native void testRegisterMethod();
}

jclass g_registerAdapter;

static JNINativeMethod gNatvieMethods[] = {
        {
                "testRegisterMethod",
                "()V",
                (void *) native_testRegisterMethod
        },
};

extern "C"
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        LOGE("**** OnLoad GetEnv failed!");
        return result;
    }

    jclass jAdapterClz = env->FindClass("com/sslyxhz/ndkcourse/NativeRegisterAdapter");
    g_registerAdapter = (jclass) env->NewGlobalRef((jobject)jAdapterClz);
    env->RegisterNatives(g_registerAdapter, gNatvieMethods, sizeof(gNatvieMethods)/ sizeof(JNINativeMethod));
    env->DeleteLocalRef(jAdapterClz);
    return JNI_VERSION_1_6;
}

extern "C"
JNIEXPORT jint JNICALL
JNI_UnLoad(JavaVM *vm, void *reserved) 
{
    // 实际上在Android平台,JNI_UnLoad不会被调用!!
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) 
    {
        LOGE("**** UnLoad GetEnv failed!");
        return result;
    }
    env->UnregisterNatives(g_registerAdapter);
    env->DeleteGlobalRef(g_registerAdapter);
    return JNI_VERSION_1_6;
}

void native_testRegisterMethod(JNIEnv *env, jobject instance) 
{
    //    do something
}

异常处理

  • JNI不对空指针、非法参数类型等错误进行检测:

    • 检查所有可能的错误会降低性能;
    • 没有足够的RunTime信息支持;
  • 当发生异常时,JNI允许Native方法处理抛出的异常,一般检测形式:

    • 发生异常时直接返回,检查JNI函数的返回值;
    • 调用ExceptionOccurred(),检测是否发生异常;
      • 异常发生后,需要清楚异常后才能继续调用其他JNI接口;
    • 未被处理的异常由JavaVM处理;
  • C++处理Java异常

    jint floor = env->CallIntMethod(obj, test_mid);
    if (env->ExceptionCheck()) {
        jclass newExcCls;
             env->ExceptionDescribe(); // 输出异常信息
             env->ExceptionClear();     // 清除异常信息
             newExcCls = env->FindClass("java/lang/IllegalArgumentException");
             if (newExcCls == NULL) {
                 return;
             }
             env->ThrowNew(newExcCls, "thrown from C code");
    }
    
  • 在可能导致异常的JNI调用后保持检测,方便定位问题

    jclass objectClass = env->GetObjectClass(obj);
    jfieldID fieldID = env->GetFieldID(objectClass, "charField", "C");
    if(env->ExceptionOccurred()) {
       return; // 检测到异常返回Java代码,在常规的Java异常流程处理
    }
    jchar result = env->GetCharField(obj, fieldID);
    

格式转换

  • Native层部分常用的类型格式转换
// jbyteArray to byte array
jbyte * arrayBody = env->GetByteArrayElements(data,0);   
jsize theArrayLengthJ = env->GetArrayLength(data);   
BYTE * starter = (BYTE *)arrayBody;   

// jbyteArray to BYTE[]
jbyte * olddata = (jbyte*)env->GetByteArrayElements(strIn, 0);  
jsize  oldsize = env->GetArrayLength(strIn);  
BYTE* bytearr = (BYTE*)olddata;  
int len = (int)oldsize;  

// BYTE[] to jbyteArray
jbyte *by = (jbyte*)pData;  
jbyteArray jarray = env->NewByteArray(nOutSize);  
env->SetByteArrayRegin(jarray, 0, nOutSize, by);  


// jbyteArray to char*
char* data = (char*)env->GetByteArrayElements(strIn, 0);  

// char* to jstring
jstring WindowsTojstring(JNIEnv* env, char* str_tmp)  
{  
     jstring rtn=0;  
     int slen = (int)strlen(str_tmp);  
     unsigned short* buffer=0;  
     if(slen == 0)  
     {  
        rtn = env->NewStringUTF(str_tmp);  
     }  
     else  
     {  
          int length = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, NULL, 0);  
          buffer = (unsigned short*)malloc(length*2+1);  
          if(MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, (LPWSTR)buffer, length) > 0)  
          {  
                rtn = env->NewString((jchar*)buffer, length);  
          }  
     }  
     if(buffer)  
     {  
        free(buffer);  
     }  
     return rtn;  
}  

// char* to jstring
JNIEXPORT jstring JNICALL Java_com_explorer_jni_SambaTreeNative_getDetailsBy  
  (JNIEnv *env, jobject jobj, jstring pc_server, jstring server_user, jstring server_passwd)  
{  
    const char *pc = env->GetStringUTFChars(pc_server, NULL);  
    const char *user = env->GetStringUTFChars(server_user, NULL);  
    const char *passwd = env->GetStringUTFChars(server_passwd, NULL);  
    const char *details = smbtree::getPara(pc, user, passwd);  
    jstring jDetails = env->NewStringUTF(details);  
    return jDetails;  
}  

参考