这个需求的环境是Android FWK层之前用HttpUrlConnection请求了系统配置Xml文件,现在需要Native化,然后需要用C++ Socket封装Http协议请求XML数据,然后将XML解析成Model然后通过JNI返回给上层的流程
简单看一下上层调用接口就知道下面要干什么了
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread() {
@Override
public void run() {
super.run();
//getHttpInfo("");
String URL = "http://rom.1919game.net:8081/NibiruRom/app/CheckVRKeyMapAction?keymapTime=1502444523662&channel=VR0829&romVersion=3&version=1";
String PATH = "/sdcard/shao2.xml";
int value = requestXmlInfo(URL,PATH);
if(value == 200){
HashMap map = getConfigMapValue(PATH);
map.size();
}else {
}
}
}.start();
}
});
native函数
public native int requestXmlInfo(String URL,String filePath);
public native HashMap getConfigMapValue(String filePath);
第一个接口requestXmlIInfo()实现网络请求然后将数据写到XML文件
第二个接口getConfigMapValue()实现将C++的Map映射到Java层的Map
下面看一下JNI层调用实现理解更好的理解C++层代码
extern "C"
JNIEXPORT jint JNICALL Java_nibiruvr_nibiru_test_com_httpcpp_MainActivity_requestXmlInfo
(JNIEnv *env, jobject obj, jstring url, jstring filePath) {
std::string URL(env->GetStringUTFChars(url, 0));
std::string FILE_PATH(env->GetStringUTFChars(filePath, 0));
Header head;
head.setHost(head.GetHostAddrExclutePortFromUrl(URL.c_str()));
head.setPath(head.GetParamFromUrl(URL.c_str()));
head.setPort(head.GetPortFromUrl(URL.c_str()));
head.setMethod("GET");
head.setAccept("text/html,application/xhtml+xml,application/xml;q=0.9,*//*;q=0.8");
head.setUserAgent("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0");
head.setAccept_language("fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3");
head.setConnexion("keep-alive");
int value;
Http http(head, &value);
if (value == 0) {
return -1;
} else {
std::string page = http.getPage(&head);
if (page.empty()) {
return -1;
}
int result = FileIo::writeContentToFile(FILE_PATH, page);
head.removeVariable();
head.removeCookie();
return result;
}
}
extern "C"
JNIEXPORT jobject JNICALL Java_nibiruvr_nibiru_test_com_httpcpp_MainActivity_getConfigMapValue
(JNIEnv *env, jobject obj,jstring filePath) {
std::string FILE_PATH(env->GetStringUTFChars(filePath, 0));
NibiruXmlParser testXmlParser;
std::map nativeMap = testXmlParser.parseConfigXml(FILE_PATH);
jobject javaHashMap;
if (nativeMap.size() > 0) {
//创建java层的hashmap
jclass class_Java_HashMap = env->FindClass("java/util/HashMap");
jmethodID java_hashMap_Init = env->GetMethodID(class_Java_HashMap, "", "()V");
javaHashMap = env->NewObject(class_Java_HashMap, java_hashMap_Init, "");
jmethodID HashMap_put = env->GetMethodID(class_Java_HashMap, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
jclass java_Config_Class = env->FindClass("nibiruvr/nibiru/test/com/httpcpp/Config");
jmethodID initMethodID = env->GetMethodID(java_Config_Class, "", "()V");
jfieldID field_name = env->GetFieldID(java_Config_Class, "packageName",
"Ljava/lang/String;");
jfieldID field_enable = env->GetFieldID(java_Config_Class, "configEnable",
"Ljava/lang/String;");
jfieldID field_scale = env->GetFieldID(java_Config_Class, "scale", "Ljava/lang/String;");
jfieldID field_stretch = env->GetFieldID(java_Config_Class, "stretch",
"Ljava/lang/String;");
jfieldID field_offsetx = env->GetFieldID(java_Config_Class, "offsetx",
"Ljava/lang/String;");
jfieldID field_offsety = env->GetFieldID(java_Config_Class, "offsety",
"Ljava/lang/String;");
jfieldID field_overlayx = env->GetFieldID(java_Config_Class, "overlayx",
"Ljava/lang/String;");
jfieldID field_overlayy = env->GetFieldID(java_Config_Class, "overlayy",
"Ljava/lang/String;");
jfieldID field_mask = env->GetFieldID(java_Config_Class, "showmask", "Ljava/lang/String;");
jfieldID field_category = env->GetFieldID(java_Config_Class, "category", "I");
jfieldID field_hwc = env->GetFieldID(java_Config_Class, "dishwc", "Ljava/lang/String;");
jfieldID field_touch = env->GetFieldID(java_Config_Class, "touch", "Ljava/lang/String;");
jfieldID field_sdk = env->GetFieldID(java_Config_Class, "sdk", "Ljava/lang/String;");
//进行迭代
for (std::map::iterator it = nativeMap.begin();
it != nativeMap.end(); it++) {
std::string configPackageName = it->first;
Config nativeConfig = it->second;
jstring packageName;
//创建Java层对象
jobject javaConfig = env->NewObject(java_Config_Class, initMethodID);
if (configPackageName.c_str() != NULL && configPackageName.length() > 0) {
packageName = (env)->NewStringUTF(configPackageName.c_str());
(env)->SetObjectField(javaConfig, field_name, packageName);
}
jstring fieldEnale = (env)->NewStringUTF(nativeConfig.enable.c_str());
jstring fieldScale = (env)->NewStringUTF(nativeConfig.scale.c_str());
jstring fieldStrech = (env)->NewStringUTF(nativeConfig.stretch.c_str());
jstring fieldOffsetx = (env)->NewStringUTF(nativeConfig.offsetx.c_str());
jstring fieldOffsety = (env)->NewStringUTF(nativeConfig.offsety.c_str());
jstring fieldOverlayx = (env)->NewStringUTF(nativeConfig.overlayx.c_str());
jstring fieldOverlayy = (env)->NewStringUTF(nativeConfig.overlayy.c_str());
jstring fieldShowmax = (env)->NewStringUTF(nativeConfig.showmask.c_str());
jstring fieldDishwc = (env)->NewStringUTF(nativeConfig.dishwc.c_str());
jstring fieldTouch = (env)->NewStringUTF(nativeConfig.touch.c_str());
jstring fieldSdk = (env)->NewStringUTF(nativeConfig.sdk.c_str());
(env)->SetObjectField(javaConfig, field_enable, fieldEnale);
(env)->SetObjectField(javaConfig, field_scale, fieldScale);
(env)->SetObjectField(javaConfig, field_stretch, fieldStrech);
(env)->SetObjectField(javaConfig, field_offsetx, fieldOffsetx);
(env)->SetObjectField(javaConfig, field_offsety, fieldOffsety);
(env)->SetObjectField(javaConfig, field_overlayx, fieldOverlayx);
(env)->SetObjectField(javaConfig, field_overlayy, fieldOverlayy);
(env)->SetObjectField(javaConfig, field_mask, fieldShowmax);
(env)->SetIntField(javaConfig, field_category, (jint) (nativeConfig.category));
(env)->SetObjectField(javaConfig, field_hwc, fieldDishwc);
(env)->SetObjectField(javaConfig, field_touch, fieldTouch);
(env)->SetObjectField(javaConfig, field_sdk, fieldSdk);
env->CallObjectMethod(javaHashMap, HashMap_put, packageName, javaConfig);
env->DeleteLocalRef(javaConfig);
env->DeleteLocalRef(packageName);
env->DeleteLocalRef(fieldEnale);
env->DeleteLocalRef(fieldScale);
env->DeleteLocalRef(fieldStrech);
env->DeleteLocalRef(fieldOffsetx);
env->DeleteLocalRef(fieldOffsety);
env->DeleteLocalRef(fieldOverlayx);
env->DeleteLocalRef(fieldOverlayy);
env->DeleteLocalRef(fieldShowmax);
env->DeleteLocalRef(fieldDishwc);
env->DeleteLocalRef(fieldTouch);
env->DeleteLocalRef(fieldSdk);
}
}
return javaHashMap;
}
注意env->DeleteLocalref()在这是为了解决JNI 512引用的限制 需要手动释放加快GC
到这我们就要开始写C++ HTTP的网络请求和TinyXml2的解析代码了,先感谢GitHUb上贡献的代码解决了Socket请求不知道数据流什么时候结束的问题
https://github.com/lucasBertola/HTTP-CLIENT
现在看一下核心代码其实就是拼HTTP协议
#include "NibiruHttp.h"
#include "rechercheInformation.h"
#include
//TODO DEV HTTPS
#define BUFLEN 20480
Http::Http(Header head, int *result) {
*result = CreateSocket(head);
*result = ConnectSocket();
}
int Http::CreateSocket(Header head) {
struct hostent *hostinfo = NULL;
const char *hostname = head.getHostChar(true);
hostinfo = gethostbyname(hostname);
if (hostinfo == NULL) {
fprintf(stderr, "Unknown host %s\n", hostname);
return 0;
}
sock = socket(AF_INET, SOCK_STREAM, 0);
sin.sin_addr = *(IN_ADDR *) hostinfo->h_addr;
sin.sin_family = AF_INET;
sin.sin_port = htons(head.getPort());
return 1;
}
int Http::ConnectSocket() {
if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
//std::cout << "Error in the connection..." << std::endl;
return 0;
}
return 1;
}
//reponce-> message renvoyer par le server dont on veut prendre les cookies.
void Http::getCookie(Header *head, std::string reponce) {
RechercheInfo::searchCutLeft(&reponce, "Set-Cookie: ", true);
std::string tampon = reponce;
while (tampon.size() != 0) {
std::string tampon2 = tampon;
RechercheInfo::searchCutRight(&tampon2, "\r\n", false);
RechercheInfo::searchCutRight(&tampon, "=", true);
RechercheInfo::searchCutLeft(&tampon2, "=", true);
if (tampon2.find(";") <= tampon2.size())
RechercheInfo::searchCutRight(&tampon2, ";", true);
else
RechercheInfo::searchCutRight(&tampon2, "\r\n", true);
head->addCookie(tampon, tampon2);
RechercheInfo::searchCutLeft(&reponce, "Set-Cookie: ", true);
tampon = reponce;
}
}
std::string Http::getPage(Header *head) {
sendPaquet(*head);
std::string reponce = recvPaquet();
getCookie(head, reponce);
head->setReferer("http://" + head->getHostString(true) + head->getPathString());
redirection(head, &reponce);
return reponce;
}
void Http::redirection(Header *head, std::string *reponce) {
if (reponce->find("\r\nlocation: ") != std::string::npos || reponce->find("\r\nLocation: ") !=
std::string::npos)//TODO changer imagine que sur le content y a un \r\nlocation
{
if (reponce->find("\r\nlocation: ") != std::string::npos)
RechercheInfo::searchCutLeft(reponce, "\r\nlocation: ", true);
if (reponce->find("\r\nLocation: ") != std::string::npos)
RechercheInfo::searchCutLeft(reponce, "\r\nLocation: ", true);
RechercheInfo::searchCutRight(reponce, "\r\n", true);
if (reponce->find("http://") == std::string::npos)
head->setPath(*reponce);
else {
std::string host = "";
std::string path = "/";
RechercheInfo::searchCutLeft(reponce, "http://", true);
if (reponce->find("/") == std::string::npos)
host = *reponce;
else {
path = *reponce;
host = *reponce;
RechercheInfo::searchCutLeft(&path, "/", false);
RechercheInfo::searchCutRight(&host, "/", true);
}
head->setHost("http://" + host);
head->setPath(path);
}
head->setMethod("GET");
head->removeVariable();
(*reponce) = getPage(head);
}
}
void Http::sendPaquet(Header head) {
//http protocol uses \r\n as a new line, and the last line must always be empty.
std::string requete = head.getMethod() + " ";
if (!head.getPathString().compare("")) {
requete += "/ ";
} else requete += head.getPathString();
requete += " HTTP/1.1\r\n";
if (head.getHostString().compare(""))
requete += "Host: " + head.getHostString() + "\r\n";
if (head.getUserAgent().compare(""))
requete += "User-Agent: " + head.getUserAgent() + "\r\n";
if (head.getAccept().compare(""))
requete += "Accept: " + head.getAccept() + "\r\n";
if (head.getAccept_language().compare(""))
requete += "Accept-Language: " + head.getAccept_language() + "\r\n";
if (head.getAccept_encoding().compare(""))
requete += "Accept-Encoding: " + head.getAccept_encoding() + "\r\n";
if (head.getRefererString().compare(""))
requete += "Referer: " + head.getRefererString() + "\r\n";
if (head.getCookie().compare(""))
requete += "Cookie: " + head.getCookie() + "\r\n";
requete += "Cookie: " + head.getCookie() + "\r\n";
if (head.getConnexion().compare(""))
requete += "Connection: " + head.getConnexion() + "\r\n";
if (head.getIfnotMatch().compare(""))
requete += "If-None-Match: " + head.getIfnotMatch() + "\r\n";
if (!head.getVariable().compare("")) {
requete += "\r\n";
} else {
std::ostringstream oss;
oss << head.getVariable().size();
requete +=
"Content-Type: application/x-www-form-urlencoded\r\nContent-Length: " + oss.str() +
"\r\n\r\n" + head.getVariable() + "\r\n\r\n";
}
char *bufferOutput = new char[requete.length() + 1];
strcpy(bufferOutput, requete.c_str());
unsigned int nbEnvoyer = 0;
int erreur = -1;
while (nbEnvoyer < strlen(bufferOutput)) {
erreur = send(sock, bufferOutput + nbEnvoyer, strlen(bufferOutput) - nbEnvoyer, 0);
nbEnvoyer += erreur;
if (erreur == 0 || erreur == -1) {
std::cout
<< "Erreur dans l'envois de la requette...La connexion a t elle pas été fermé?. "
<< erreur << std::endl;//TODO GERER UNE RECONNEXION
}
}
delete[] bufferOutput;
}
int Http::recvTimeOut(unsigned int sock, int millisecond, std::string *chaine) {
struct timeval timeout;
timeout.tv_sec = millisecond;
timeout.tv_usec = 0;
fd_set readfs;
FD_ZERO(&readfs);
FD_SET(sock, &readfs);
int nb = select(sock + 1, &readfs, NULL, NULL, &timeout);
if (nb == 0) {
return 0;
} else if (nb == -1) {
//CreateSocket(head);
ConnectSocket();
return 0;
} else {
char recvbuf[BUFLEN];
int recvbuflen = BUFLEN;
int result = recv(sock, recvbuf, recvbuflen, 0);
for (int i = 0; i < result; i++)
(*chaine) += recvbuf[i];
return result;
}
}
int Http::hexaTodecimal(std::string hex) {
int x;
std::istringstream iss(hex);
iss >> std::hex >> x;
return x;
}
unsigned int Http::tailleHead(std::string page) {
int taille = page.length();
RechercheInfo::searchCutRight(&page, "\r\n\r\n", false);
if (page.length() == 0)
return taille;
return page.length();
}
std::string Http::recvPaquet() {
//stock the number of bytes received
unsigned int nbRecu = 0;
//the number of bytes of the chunk.
unsigned int chunked = 0;
bool isChunked = false;
bool haveContentLength = false;
//the number of bytes of the content.
unsigned int contentLenght = 0;
//Stock the reponse
std::string input = "";
bool isRunning = true;
unsigned int tampon;
while (isRunning) {
tampon = recvTimeOut(sock, 2000, &input);
nbRecu += tampon;
if (tampon == 0) {
break;
}
if (!isChunked && input.find("Transfer-Encoding: chunked") != std::string::npos &&
input.find("Transfer-Encoding: chunked") < tailleHead(input)) {
std::string tampon = input;
tampon.replace(nbRecu, tampon.size(), "");
RechercheInfo::searchCutLeft(&tampon, "\r\n\r\n", true);
RechercheInfo::searchCutRight(&tampon, "\r", true);
if (tampon.size() > 0) {
chunked = hexaTodecimal(tampon) + input.find(tampon) + tampon.size() + 4;
isChunked = true;
input.replace(input.find(tampon), tampon.size() + 2, "");
nbRecu -= (tampon.length() + 2);
chunked -= (tampon.length() + 2);
}
}
while (isChunked && chunked < nbRecu) {
std::string tampon = input;
tampon.replace(0, chunked, "");
RechercheInfo::searchCutRight(&tampon, "\r", true);
//POUR GERER LE cas ou le input s'arrete pil sur la valeur hexa
if (tampon.length() == 0)
break;
int nombre = hexaTodecimal(tampon);
//if the size of the new chunk is 0 we stop.
if (nombre == 0) {
isRunning = false;
break;
} else //else we upgrade the chunk
{
int EmplacementChunked = 0;
if (input.find(tampon) != std::string::npos)
EmplacementChunked = input.find(tampon);
if (nbRecu < (EmplacementChunked + tampon.length() + 2)) break;
input.replace(EmplacementChunked - 2, tampon.length() + 4, "X");
nbRecu -= (tampon.length() + 3);
chunked += nombre + 1;// en faite ce serait plus : -2 +2 + nombre
}
}
if (!haveContentLength) {
std::string tampon = input;
//on garde uniquement le header
tampon.replace(tailleHead(input), tampon.size() - tailleHead(input), "");
RechercheInfo::searchCutLeft(&tampon, "Content-Length:", true);
RechercheInfo::searchCutRight(&tampon, "\r", true);
if (tampon.size() > 0) {
std::istringstream iss(tampon);
iss >> contentLenght;
haveContentLength = true;
}
}
if (haveContentLength && (contentLenght + tailleHead(input)) <= nbRecu) {
break;
}
}
return input;
}
Http::~Http() {
closesocket(sock);
}
现在开始解析XML了 用的是开源的TinyXml2
std::map NibiruXmlParser::parseConfigXml(const std::string filePath) {
XMLDocument doc;
doc.LoadFile(filePath.c_str());
XMLElement *rootElement = doc.RootElement();
XMLElement *packElement = rootElement->FirstChildElement("package");
std::map vrConfigMap;
vrConfigMap.clear();
while (packElement) {
//获得packageName
Config modelConfig;
XMLElement *packageChild = packElement->FirstChildElement();
const XMLAttribute *attributeOfSurface = packElement->FirstAttribute();
modelConfig.packageName = attributeOfSurface->Value();
while (packageChild) {
const std::string childName = packageChild->Name();
std::string value;
if (packageChild->GetText() == NULL) {
value = "";
} else {
value = packageChild->GetText();
}
if (childName.compare("enable") == 0) {
modelConfig.enable = value;
} else if (childName.compare("scale") == 0) {
modelConfig.scale = value;
} else if (childName.compare("stretch") == 0) {
modelConfig.stretch = value;
} else if (childName.compare("offsetx") == 0) {
modelConfig.offsetx = value;
} else if (childName.compare("offsety") == 0) {
modelConfig.offsety = value;
} else if (childName.compare("overlayx") == 0) {
modelConfig.overlayx = value;
} else if (childName.compare("overlayy") == 0) {
modelConfig.overlayy = value;
} else if (childName.compare("mask") == 0) {
modelConfig.showmask = value;
} else if (childName.compare("category") == 0) {
modelConfig.category = atoi(value.c_str());
} else if (childName.compare("hwc") == 0) {
modelConfig.dishwc = value;
} else if (childName.compare("touch") == 0) {
modelConfig.touch = value;
} else if (childName.compare("sdk_distort") == 0) {
modelConfig.sdk = value;
}
packageChild = packageChild->NextSiblingElement();
}
vrConfigMap.insert(std::pair(modelConfig.packageName, modelConfig));
packElement = packElement->NextSiblingElement();
}
return vrConfigMap;
};
到这C++--》JNI--》JAVA就完成需求了
下面就是我Demo实现的GitHub地址 欢迎Star
https://github.com/ShaoZhongqi2359/JNI_CPP_HTTP_XMLParser