一 环境的搭建
下载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/