【Android(安卓)JNI】Native层解析Java复杂数据类型HashMap
前提
Java HashMap 是基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用null
值和null
键。HashMap是存放引用类型数据的容器,只能存放引用数据类型,不能存放如int、long等基础类型的数据。
这里用实际的例子来演示如何解析HashMap,在这个Sample中,HashMap作为参数从Java传递到Native(C/C++)层,然后在C代码中解析HashMap中的数据。Java中使用HashMap
存放学生成绩 “课程 - 分数”键值对,然后把这个学生成绩HashMap数据传递到Native中,用C/C++解析其中的数据,然后以字符串的形式把成绩信息返回到Java。
准备工作:Java中构造和解析HashMap
首先,在Java中构造一个课程分数HashMap。
public HashMap constructHashMap() { HashMap 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 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 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 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
结构的课程成绩数据。
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
函数 和 调用public String parseHashMap2(HashMap
函数效果一样,都能返回下面这个成绩信息字符串。
"Physics: 100. Math: 95. Chinese: 90. Biology: 85. "
如果疑问,欢迎留言。
更多相关文章
- 一句话锁定MySQL数据占用元凶
- Android:android的框架区别(网络框架、图片异步加载与缓存框架、数
- Android之四大组件(Service的开启与关闭)
- Android用户界面开发:事件处理
- android 完整地操作数据库--日记本实例
- Android获取父类容器中控件的方法
- Android中个人推崇的数据库使用方式
- Android关于定时器Timer的定义及用法
- AsyncTask的使用半解--!