完整的JNI 例子和总结

it2024-08-10  39

一 环境的搭建

下载DNK,在配置sdk地方配置ndk 的本地路径,或者在build.gradle 的配置文件中配置ndk 的路径,通过同步也可以配置ndk 环境,NDK 开发中涉及调试,确保

在SDK tools 中安装,在开始开发的时候,一定要把开发环境&调试环境设置好,我在开发的时候遇到环境没法办法调试,网上能找到的办法都试了一遍,还是没有办法解决,后来干脆重新创建个新项目,这个项目在创建的时候选择C++ project ,然后将之前的项目直接拷贝进去, 可以debug了,可能还是工程环境的原因导致。

还有个原因我的开发机器是华为机器,后来换成三星或者魅族的手机都可以调试了

跑起来调试也是个超级开心的事情。

二,CMakeLists.txt 配置

现在涉及JNI 开发已经不用原来的android.mk 方式配置了,大部分都是用使用CMakeLists.txt配置,什么都不说了,直接上配置的修改

# Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.4.1) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. # # 指定so生成到libs目录 add_definitions(-std=c++11) FILE(GLOB Source_SRC source/*.cpp) add_library( # Sets the name of the library. source # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). # com_example_diff_BsDiffUtils.c # ${Source_SRC} ) # ## Searches for a specified prebuilt library and stores the path as a ## variable. Because CMake includes system libraries in the search path by ## default, you only need to specify the name of the public NDK library ## you want to add. CMake verifies that the library exists before ## completing its build. # find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log) ## Specifies libraries CMake should link to your target library. You ## can link multiple libraries, such as libraries you define in this ## build script, prebuilt third-party libraries, or system libraries. # target_link_libraries( # Specifies the target library. source # Links the target library to the log library # included in the NDK. ${log-lib})

1,cmake_minimum_required(VERSION 3.4.1)

配置版本号,基本都是这样配置

2,FILE(GLOB Source_SRC source/*.cpp)

这个地方是配置C/C++ 源码的存放路径,这个地方是配置在source 文件夹下,对外开放的链接别名是Source_SRC。其中GLOB是全部文件的意思,只支持单个文件夹的源码依赖形式,如果source 文件夹中有含有子文件夹,CMAKE 语法中需要改成GLOB_RESOURCE,但是没有设置成功,

异常java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList]

后续需要花点时间调研这个问题。

 

三 对应java 对象创建

import java.util.ArrayList; import java.util.HashMap; public class NativeValue { public boolean boolValue; public int intValue; public double doubleValue; public String stringValue; public ArrayList arrayValue; public HashMap<String, NativeValue> mapValue; public String valueType; public NativeValue() { } public NativeValue(HashMap<String, NativeValue> mapValue) { this.setMapValue(mapValue); } public NativeValue(String stringValue) { this.setStringValue(stringValue); } public NativeValue(double doubleValue) { this.setDoubleValue(doubleValue); } public NativeValue(ArrayList arrayValue) { this.setArrayValue(arrayValue); } public NativeValue(int intValue) { this.setIntValue(intValue); } public NativeValue(boolean boolValue) { this.setBoolValue(boolValue); } public String getStringValue() { return this.stringValue; } public boolean getBoolValue() { return this.boolValue; } public double getDoubleValue() { return this.doubleValue; } public int getIntValue() { return this.intValue; } public String getValueType() { return this.valueType; } public HashMap<String, NativeValue> getMapValue() { return this.mapValue; } public ArrayList<NativeValue> getArrayValue() { return this.arrayValue; } public void setMapValue(HashMap<String, NativeValue> mapValue) { this.mapValue = mapValue; this.valueType = NativeValueType.TypeMap; } public void setStringValue(String stringValue) { this.stringValue = stringValue; this.valueType = NativeValueType.TypeString; } public void setArrayValue(ArrayList arrayValue) { this.arrayValue = arrayValue; this.valueType = NativeValueType.TypeArray; } public void setDoubleValue(double doubleValue) { this.doubleValue = doubleValue; this.valueType = NativeValueType.TypeDouble; } public void setIntValue(int intValue) { this.intValue = intValue; this.valueType = NativeValueType.TypeInt; } public void setBoolValue(boolean boolValue) { this.boolValue = boolValue; this.valueType =NativeValueType.TypeBool; } } public class NativeValueType { public static final String TypeString = "TypeString"; public static final String TypeMap = "TypeMap"; public static final String TypeInt = "TypeInt"; public static final String TypeDouble = "TypeDouble"; public static final String TypeBool = "TypeBool"; public static final String TypeObject = "TypeObject"; public static final String TypeArray = "TypeArray"; public static final String TypeNull = "TypeNull"; }

这个地方是包含了大部分类型数据,创建cpp 和 hpp 两个文件,hpp 文件是用来声明方法的

那么先看下NativeValue 中如何对String 类型数据进行传递的,首先室java->C++

CValue convertToCValueFromJavaObject(JNIEnv *env, jclass clazz, jobject obj) { CValue value; //Cvalue 是个C++类,我么这里就是讲NatvieValue转化成CValue类型 jclass valueInfo = env->GetObjectClass(obj); jmethodID m_HashMap = env->GetMethodID(valueInfo, "getMapValue", "()Ljava/util/HashMap;");// 获取NavtiveValue 中的获取map 的方法 jmethodID m_string = env->GetMethodID(valueInfo, "getStringValue", "()Ljava/lang/String;");// 获取NativeValue 中的获取String 的方法 jobject hashMapObj = (jobject) env->CallObjectMethod(obj, m_HashMap); jstring stringObj = (jstring) env->CallObjectMethod(obj, m_string); jfieldID f_List = env->GetFieldID(valueInfo, "arrayValue", "Ljava/util/ArrayList;"); // 获取 NativeValue 中arrayValue 属性,这个地方不同于其他资料中的取ArrayList 的方式, // 这里是将整个对象jobject传递进来,然后获取jobject 中的arrayValue 中的值 jobject arrayObj = env->GetObjectField(obj, f_List); jfieldID f_double = env->GetFieldID(valueInfo, "doubleValue", "D"); // 获取double 属性 jdouble doubleObj = (jdouble) env->GetDoubleField(obj, f_double); jfieldID f_int = env->GetFieldID(valueInfo, "intValue", "I"); //获取int 属性 jint intObj = (jdouble) env->GetIntField(obj, f_int); jfieldID f_bool = env->GetFieldID(valueInfo, "boolValue", "Z"); //获取bool 属性 jboolean boolObj = (jboolean) env->GetBooleanField(obj, f_bool); if (hashMapObj != NULL) { std::map<std::string, CValue> map = convertToCMapFromJavaJSONObj(env, clazz, hashMapObj); value = CValue(map); return value; } else if (stringObj != NULL) { std::string stringValue = convertToCValueFromJavaString(env, clazz, stringObj); value = CValue(stringValue); return value; } else if (arrayObj != NULL) { std::vector<CValue> array = convertToCValueMapFromJavaArray(env, clazz, arrayObj); value = CValue(array); return value; } else if (doubleObj != NULL) { value = CValue(doubleObj); return value; } else if (intObj != NULL) { value = CValue(intObj); return value; } else if (boolObj != NULL) { value = CValue(); value.setBoolValue(boolObj); return value; } return CValue(); }

我们先看下java-》C++ 转换中对hashMap类型转换的方法 convertToCMapFromJavaJSONObj 的具体实现

std::map<std::string, CValue> convertToCValueMapFromJavaJSONObj(JNIEnv *env, jclass clazz, jobject hashMapObj) { std::map<std::string, CValue> dataMap; // 获取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(hashMapObj, entrySetMID); // 调用size()方法获取HashMap键值对数量 // 获取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中是否还有数据 while (env->CallBooleanMethod(iteratorObj, hasNextMID)) { // 读取一条数据 jobject entryObj = env->CallObjectMethod(iteratorObj, nextMID); jstring mapKey = (jstring) env->CallObjectMethod(entryObj, getKeyMID); if (mapKey == NULL) // HashMap允许null类型 continue; const char *jstrChar = (char *) env->GetStringUTFChars(mapKey, 0); std::string keyStr = jstrChar; jobject valueObj = env->CallObjectMethod(entryObj, getValueMID); // 这个地方室递归调用转化对象的方法,可以处理map 中value 存在嵌套的场景 CValue value = convertToCValueFromJavaObject(env, clazz, valueObj); dataMap.insert(pair<std::string, CValue>(keyStr, value)); } return dataMap; }

我们再看下java-> C++ 中 ArrayList 是如何转化的 ,对应方法是 convertToCValueMapFromJavaArray

std::vector<CValue> convertToCValueMapFromJavaArray(JNIEnv *env, jclass clazz, jobject arrayObj) { std::vector<CValue> vector; jclass cArrayList = env->GetObjectClass(arrayObj); jmethodID arrayList_get = env->GetMethodID(cArrayList, "get", "(I)Ljava/lang/Object;"); jmethodID arrayList_size = env->GetMethodID(cArrayList, "size", "()I"); jint len = env->CallIntMethod(arrayObj, arrayList_size); for (int i = 0; i < len; ++i) { jobject obj_value = env->CallObjectMethod(arrayObj, arrayList_get, i); // 这个地方递归调用,支持ArrayList 中存放NativeValue 类似的数据 CValue value = convertToCValueFromJavaObject(env, clazz, obj_value); vector.push_back(value); } return vector; }

再看下java->C++ 中 String 类型的数据转化,具体实现方法看下 convertToCValueFromJavaString

std::string convertToCValueFromJavaString(JNIEnv *env, jclass clazz, jstring jstringObj) { const char *jstrChar = (char *) env->GetStringUTFChars(jstringObj, 0); std::string str = jstrChar; return str; }

上面讲完了java->C++ 的过程,接下来我们讲下 C++ -> java 过程的实现方式

 

jobject convertToJavaObjectFromCValue(JNIEnv *env, CValue value) { // 这里是转向natvie class 对应的路径 jclass c_value = env->FindClass("com/**/**/**/ValueNative"); jfieldID jfValueType = env->GetFieldID(c_value, "valueType", "Ljava/lang/String;"); jobject joInfo = env->AllocObject(c_value); // 生成String 对象,根据valueType去区分要处理那种类型的数据 if (CValue.valueType == TypeString) { std::string str = value.getStringValue(); const char *resultStr = str.c_str(); jfieldID jfs = env->GetFieldID(c_value, "stringValue", "Ljava/lang/String;"); jstring jResultStr = env->NewStringUTF(resultStr); env->SetObjectField(joInfo, jfs, jResultStr); std::string typeStr = "MorTypeString"; const char *typeChar = typeStr.c_str(); jstring jTypeStr = env->NewStringUTF(typeChar); env->SetObjectField(joInfo, jfValueType, jTypeStr); return joInfo; } // 生成map 对象 else if (value.valueType == TypeMap) { std::map<std::string, CValue> map = value.getMapValue(); jclass hashMapClass = env->FindClass("java/util/HashMap"); //这个是创建无参数的构造方法 jmethodID hashMapInit = env->GetMethodID(hashMapClass, "<init>", "(I)V"); jobject hashMapObj = env->NewObject(hashMapClass, hashMapInit, map.size()); jmethodID hashMapOut = env->GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); std::map<std::string, MorValue>::const_iterator it; for (it = map.begin(); it != map.end(); ++it) { std::string str = it->first; const char *chardata = str.c_str(); jstring string = env->NewStringUTF(chardata);; CValue value = (CValue) it->second; // 这个地方室采用递归方式 jobject info = convertToJavaObjectFromCValue(env, value); env->CallObjectMethod(hashMapObj, hashMapOut, string, info); jfieldID jfl = env->GetFieldID(c_value, "mapValue", "Ljava/util/HashMap;"); env->SetObjectField(joInfo, jfl, hashMapObj); std::string typeStr = "MorTypeMap"; const char *typeChar = typeStr.c_str(); jstring jTypeStr = env->NewStringUTF(typeChar); env->SetObjectField(joInfo, jfValueType, jTypeStr); } return joInfo; } else if (value.valueType == TypeInt) { jfieldID jfl = env->GetFieldID(c_value, "intValue", "I"); env->SetIntField(joInfo, jfl, value.getIntValue()); std::string typeStr = "MorTypeInt"; const char *typeChar = typeStr.c_str(); jstring jTypeStr = env->NewStringUTF(typeChar); env->SetObjectField(joInfo, jfValueType, jTypeStr); return joInfo; } else if (value.valueType == TypeDouble) { jfieldID jfd = env->GetFieldID(c_value, "doubleValue", "D"); env->SetDoubleField(joInfo, jfd, value.getDoubleValue()); std::string typeStr = "TypeDouble"; const char *typeChar = typeStr.c_str(); jstring jTypeStr = env->NewStringUTF(typeChar); env->SetObjectField(joInfo, jfValueType, jTypeStr); return joInfo; } else if (value.valueType == TypeBool) { jfieldID jfd = env->GetFieldID(c_value, "boolValue", "Z"); env->SetBooleanField(joInfo, jfd, value.getBoolValue()); std::string typeStr = "TypeBool"; const char *typeChar = typeStr.c_str(); jstring jTypeStr = env->NewStringUTF(typeChar); env->SetObjectField(joInfo, jfValueType, jTypeStr); return joInfo; } else if (value.valueType == TypeArray) { std::vector<CValue> array = value.getArrayValue(); jclass list_jcs = env->FindClass("java/util/ArrayList"); //获取ArrayList构造函数id jmethodID list_init = env->GetMethodID(list_jcs, "<init>", "()V"); //创建一个ArrayList对象 jobject list_obj = env->NewObject(list_jcs, list_init); //获取ArrayList对象的add()的methodID jmethodID list_add = env->GetMethodID(list_jcs, "add", "(Ljava/lang/Object;)Z"); jfieldID jArrayList = env->GetFieldID(c_value, "arrayValue", "Ljava/util/ArrayList;"); for (int i = 0; i < array.size(); ++i) { // 这个地方是递归,支持ArrayList 中的元素还是本身类型的 jobject info = convertToJavaObjectFromCValue(env, (CValue) array[i]); env->CallBooleanMethod(list_obj, list_add, info); } env->SetObjectField(joInfo, jArrayList, list_obj); std::string typeStr = "TypeArray"; const char *typeChar = typeStr.c_str(); jstring jTypeStr = env->NewStringUTF(typeChar); env->SetObjectField(joInfo, jfValueType, jTypeStr); return joInfo; } env->DeleteLocalRef(c_value); return joInfo; }

我们顺便看下我们声明的  hpp 文件中的声明形式

#include <jni.h> #include "CValue.hpp" #include <string> #include <map> using namespace std; using namespace VMSpace; extern "C" { JNIEXPORT jobject JNICALL Java_com_**_**_**_JNINative_JNIConvertToAndroidObjectFromCValue (JNIEnv *, jclass, jstring); JNIEXPORT void JNICALL Java_com_**_**_**_JNINative_JNIConvertToCValueFromAndroidObject (JNIEnv *, jclass, jobject); // java->c++ std::map<std::string, CValue> convertToCValueMapFromJavaJSONObj(JNIEnv *, jclass clazz, jobject obj); std::string convertToMorValueFromJavaString(JNIEnv *, jclass clazz, jstring jstring); CValue convertToCValueFromJavaObject(JNIEnv *, jclass clazz, jobject obj); std::vector<CValue> convertToCValueMapFromJavaArray(JNIEnv *, jclass clazz, jobject obj); // c++ -> java jobject convertToJavaObjectFromCValue(JNIEnv *, CValue value); };

开发中涉及C++ 技术栈,主要涉及以下几点

1  jstring 和 C++ String 的转化

C++ String 可以与const char *a 相等,所以涉及到jstring 和 char 之前的转化

char -> jstring 

采用 env->newStringUTF

 char->std::string

 std::string str = value.getStringValue();  const char *resultStr = str.c_str();

jstring ->char const char *jstrChar = (char *) env->GetStringUTFChars(jstringObj, 0);

 

参考资料:

1  http://gityuan.com/2016/05/28/android-jni/

最新回复(0)