【Android JNI】Native層解析Java複雜數據類型HashMap

前提

Java HashMap 是基於哈希表的 Map 接口的實現。此實現提供所有可選的映射操作,並允許使用null值和null鍵。HashMap是存放引用類型數據的容器,只能存放引用數據類型,不能存放如int、long等基礎類型的數據。
這裏用實際的例子來演示如何解析HashMap,在這個Sample中,HashMap作爲參數從Java傳遞到Native(C/C++)層,然後在C代碼中解析HashMap中的數據。Java中使用HashMap<String, Integer>存放學生成績 “課程 - 分數”鍵值對,然後把這個學生成績HashMap數據傳遞到Native中,用C/C++解析其中的數據,然後以字符串的形式把成績信息返回到Java。

準備工作:Java中構造和解析HashMap

首先,在Java中構造一個課程分數HashMap。

public HashMap<String, Integer> constructHashMap() {
    HashMap<String, Integer> courseScore = new HashMap<>();
    courseScore.put("Chinese", 90);
    courseScore.put("Math", 95);
    courseScore.put("Physics", 100);
    courseScore.put("Biology", 85);
    return courseScore;
}

在Java中實現返回字符串需求的話,可以使用下面的代碼來解析HashMap中的數據。

public String parseHashMap(HashMap<String, Integer> courseScore){
    StringBuilder info = new StringBuilder();

    Iterator iter = courseScore.entrySet().iterator();
    while (iter.hasNext()){
        Map.Entry entry = (Map.Entry) iter.next();
        String course = (String) entry.getKey();
        Integer score = (Integer) entry.getValue();

        info.append(course+": ");
        info.append(score+". ");
    }
    return String.valueOf(info);
}

在主函數中依次調用constructHashMap()parseHashMap()函數,會返回如下所示的字符串:

"Physics: 100. Math: 95. Chinese: 90. Biology: 85. "

Native層雖然使用的是C/C++編程語言,但是使用的確實Java的處理邏輯。在Native中解析HashMap數據結構就要完全按照parseHashMap()函數的邏輯進行數據處理。說白了,就是使用C/C++來模仿Java完成相應操作。
在Native中模仿Java操作其實不難,只是比較 繁瑣 !本來Java一句代碼可能就需要四五句甚至更多語句來實現相同的功能。爲了在Native中能更容易的模仿Java實現,現在我們來把上面的parseHashMap()函數中的語句儘量使用簡單語句來實現。

public String parseHashMap2(HashMap<String, Integer> courseScore){
    String info = "";

    Set set = courseScore.entrySet();
    Iterator iter = set.iterator();

    while (iter.hasNext()){
        Map.Entry entry = (Map.Entry) iter.next();
        String course = (String) entry.getKey();
        info = info + course + ": ";

        Integer score = (Integer) entry.getValue();
        info = info + score.intValue() + ". ";
    }
    return info;
}

進入正題:用C/C++解析Java HashMap

首先Java中聲明Native函數。

public class JniManager {
    ...
    public native String nGetHashMapInfo(HashMap<String, Integer> info);
}

JNI_OnLoad函數中,函數名映射關係結構體JNINativeMethod中有如下描述。

static const JNINativeMethod gMethods[] = {
        {"nGetHashMapInfo", "(Ljava/util/HashMap;)Ljava/lang/String;", (void *) jniGetHashMapInfo}
};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    ...
    jint count = sizeof(gMethods) / sizeof(gMethods[0]);

    if (env->RegisterNatives(clazz, gMethods, count) != JNI_OK) {
        return result;
    }
    ....
}

在上一篇博客【Android JNI】在C/C++中調用Java中講到,調用Java方法的步驟爲:1. 獲取類名jclass,2.獲取方法ID jmethodID,3. 使用實例對象jobject和jmethod調用相應的方法。也就是說每次使用一個方法,都要先通過類獲取到方法ID,並且要得到對象jobject
我們在jniGetHashMapInfo()函數中解析Java HashMap<String, Integer>結構的課程成績數據。

jstring jniGetHashMapInfo(JNIEnv *env, jobject object, jobject hashMapInfo) {
    // 用於拼接字符串的數組
    char buff[100] = {0};
    // 用於拼接字符串的“遊標”指針
    char *pos = buff;

    if (hashMapInfo == NULL)
        return env->NewStringUTF(buff);

    // 獲取HashMap類entrySet()方法ID
    jclass hashmapClass = env->FindClass("java/util/HashMap");
    jmethodID entrySetMID = env->GetMethodID(hashmapClass, "entrySet", "()Ljava/util/Set;");
    // 調用entrySet()方法獲取Set對象
    jobject setObj = env->CallObjectMethod(hashMapInfo, entrySetMID);
    // 調用size()方法獲取HashMap鍵值對數量
//  jmethodID sizeMID = env->GetMethodID(hashmapClass, "size", "()I");
//  jint size = env->CallIntMethod(hashMapInfo, sizeMID);

    // 獲取Set類中iterator()方法ID
    jclass setClass = env->FindClass("java/util/Set");
    jmethodID iteratorMID = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
    // 調用iterator()方法獲取Iterator對象
    jobject iteratorObj = env->CallObjectMethod(setObj, iteratorMID);

    // 獲取Iterator類中hasNext()方法ID
    // 用於while循環判斷HashMap中是否還有數據
    jclass iteratorClass = env->FindClass("java/util/Iterator");
    jmethodID hasNextMID = env->GetMethodID(iteratorClass, "hasNext", "()Z");
    // 獲取Iterator類中next()方法ID
    // 用於讀取HashMap中的每一條數據
    jmethodID nextMID = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");

    // 獲取Map.Entry類中getKey()和getValue()的方法ID
    // 用於讀取“課程-分數”鍵值對,注意:內部類使用$符號表示
    jclass entryClass = env->FindClass("java/util/Map$Entry");
    jmethodID getKeyMID = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
    jmethodID getValueMID = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");

    // HashMap只能存放引用數據類型,不能存放int等基本數據類型
    // 使用Integer類的intValue()方法獲取int數據
    jclass integerClass = env->FindClass("java/lang/Integer");
    jmethodID valueMID = env->GetMethodID(integerClass, "intValue", "()I");

    // 循環檢測HashMap中是否還有數據
    while (env->CallBooleanMethod(iteratorObj, hasNextMID)) {
        // 讀取一條數據
        jobject entryObj = env->CallObjectMethod(iteratorObj, nextMID);

        // 提取數據中key值:String類型課程名字
        jstring courseJS = (jstring) env->CallObjectMethod(entryObj, getKeyMID);
        if (courseJS == NULL)   // HashMap允許null類型
            continue;
        // jstring轉C風格字符串
        const char *courseStr = env->GetStringUTFChars(courseJS, NULL);

        // 提取數據中value值:Integer類型分數,並轉爲int類型
        jobject scoreObj = env->CallObjectMethod(entryObj, getValueMID);
        if (scoreObj == NULL)
            continue;
        int score = (int) env->CallIntMethod(scoreObj, valueMID);

        // 拼接字符串,sprintf函數返回拼接字符個數
        int strLen = sprintf(pos, "%s: ", courseStr);
        pos += strLen;
        int numLen = sprintf(pos, "%d. ", score);
        pos += numLen;

        // 釋放UTF字符串資源
        env->ReleaseStringUTFChars(courseJS, courseStr);
        // 釋放JNI局部引用資源
        env->DeleteLocalRef(entryObj);
        env->DeleteLocalRef(courseJS);
        env->DeleteLocalRef(scoreObj);
    }

    // 釋放JNI局部引用: jclass jobject
    env->DeleteLocalRef(hashmapClass);
    env->DeleteLocalRef(setObj);
    env->DeleteLocalRef(setClass);
    env->DeleteLocalRef(iteratorObj);
    env->DeleteLocalRef(iteratorClass);
    env->DeleteLocalRef(entryClass);
    env->DeleteLocalRef(integerClass);

    // 生成jstring字符串並返回
    return env->NewStringUTF(buff);
}

調用public native String nGetHashMapInfo(HashMap<String, Integer> info); 函數 和 調用public String parseHashMap2(HashMap<String, Integer> courseScore)函數效果一樣,都能返回下面這個成績信息字符串。

"Physics: 100. Math: 95. Chinese: 90. Biology: 85. "

如果疑問,歡迎留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章