通过NDK使用c++库文件在子线程中网络请求以及文件下载
1.有时候需要对自己的网络请求数据逻辑进行加密
2.在cpp文件中创建子线程
3.子线程中回调原生方法
准备工作:
需要能支持cmake的Androidstudio版本
编译下载curl,如果没有可以直接使用我的文件,这里编译下载就不多说了。
----------------------------------2021年1月20日更新----------------------------------------------------------
有些小伙伴说需要支持arm64位的,在github 上我已经更新版本 支持各种架构。
并且舍弃静态a库改为动态so库,并且上传源码。小伙伴可以按需编译。
这里可以选择构建版本
然后通过
ndk-build
进行编译。
代码:
首先说的是定义的native方法:
有post get 文件下载 以及请求完成和下载进度的回调
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Toast;
/**
*@author chenz
*@Desc 用于请求 数据下载文件
**/
public class NativeUtils {
static {
System.loadLibrary("native-lib");
}
Context context;
View view;
Handler handler;
public NativeUtils(Context context, View view, Handler handler){
this.context = context;
this.view =view;
this.handler =handler;
}
/**
*注意下面的方法均是在子线程中完成的
**/
public native String NativeHttpGet(String url);
public native String NativeHttpPost(String url,String reqJson);
public native String NativeHttpDownLoad(String fileurl,String savepath);
public void callBackFromNDK(int type,String msg){
if(view!=null) {
view.post(() -> {
Toast.makeText(context, msg + "--type:" + type, Toast.LENGTH_LONG).show();
});
}
Message message = new Message();
message.what =type;
message.obj = msg;
handler.sendMessage(message);
}
public void callProgressFromNDK(double progress){
if (progress==100){
System.out.println("下载完成");
}
}
}
接下来就是对应的native方法的实现:
#include "native-lib.h"
#include "CommonTools.h"
#ifdef __cplusplus
extern "C"
#endif
Http_Get(jstring ,NativeHttpGet,jstring url_) {
string hello = "";
const char * url = env->GetStringUTFChars(url_,0);
jstring params =env->NewStringUTF(hello.c_str());
jobject_global = env->NewGlobalRef(instance);
Message *message = new Message();
message->time= 10;
message->type=1;
message->url= const_cast<char *>(url);
message->callBackInThread=&SendMsgToJava;
//param1 子线程 param2 基本没有标识 3params 回调方法void* method(void* args) 4参数 void*
pthread_create(&pid, 0, callCreateThread, message);
return params;
}
#ifdef __cplusplus
extern "C"
#endif
Http_Post(jstring ,NativeHttpPost,jstring url_,jstring reqJson_) {
string hello = "";
const char * url = env->GetStringUTFChars(url_,0);
const char * reqJson = env->GetStringUTFChars(reqJson_,0);
jstring back =env->NewStringUTF(hello.c_str());
jobject_global = env->NewGlobalRef(instance);
Message *message = new Message();
message->time= 10;
message->type=2;
message->url= const_cast<char *>(url);
message->reqJson = const_cast<char*>(reqJson);
message->callBackInThread=&SendMsgToJava;
//param1 子线程 param2 基本没有标识 3params 回调方法void* method(void* args) 4参数 void*
pthread_create(&pid, 0, callCreateThread, message);
return back;
}
#ifdef __cplusplus
extern "C"
#endif
Http_DownLoad(jstring ,NativeHttpDownLoad,jstring url_,jstring savePath_) {
string hello = "";
const char * url = env->GetStringUTFChars(url_,0);
const char * savePath = env->GetStringUTFChars(savePath_,0);
jstring params =env->NewStringUTF(hello.c_str());
jobject_global = env->NewGlobalRef(instance);
Message *message = new Message();
message->time= 10;
message->type=3;
message->url= const_cast<char *>(url);
message->savePath=const_cast<char *>(savePath);
message->callBackInThread=&SendMsgToJava;
// param1 子线程 param2 基本没有标识 3params 回调方法void* method(void* args) 4参数 void*
pthread_create(&pid, 0, callCreateThread, message);
return params;
}
//当调用System.loadLibrary时,会回调这个方法 ,此时获取全局的Javavm
jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
javaVM = vm;
return JNI_VERSION_1_6;
}
//回调信息给java
void* SendMsgToJava(int type, const char *result) {
JNIEnv *env;
javaVM->AttachCurrentThread(&env, NULL);
jclass classCallback = env->GetObjectClass(jobject_global);
jmethodID methodErr = env->GetMethodID(classCallback, "callBackFromNDK", "(ILjava/lang/String;)V");
env->CallVoidMethod(jobject_global, methodErr, type,env->NewStringUTF(result));
javaVM->DetachCurrentThread();
return 0;
}
//回调下载进度给Java,这里可以优化一下 每个回调加一个唯一标识符合
void* SendProToJava(double progress ) {
JNIEnv *env;
javaVM->AttachCurrentThread(&env, NULL);
jclass classCallback = env->GetObjectClass(jobject_global);
jmethodID methodErr = env->GetMethodID(classCallback, "callProgressFromNDK", "(D)V");
env->CallVoidMethod(jobject_global, methodErr, progress);
javaVM->DetachCurrentThread();
return 0;
}
void* callCreateThread( void* args ){
CommonTools * commonTools ;
Message* message =(Message*)args;
LOGI("超时时间:%d",message->time);
LOGI("请求地址:%s",message->url);
LOGI("请求类型:%d",message->type);
LOGI("请求参数:%s",message->reqJson);
LOGI("保存路径:%s",message->savePath);
string strResponse ;
int ret;
switch (message->type){
case 1:
ret= commonTools->HttpGet( message->url, strResponse, message->time);
if (ret==0){
message->callBackInThread(message->type,strResponse.c_str());
} else{
message->callBackInThread(-(message->type),strResponse.c_str());
}
LOGI("GET请求结果:%s,返回的值是:%d",strResponse.c_str(),ret);
break;
case 2:
ret= commonTools->HttpPost( message->url, message->reqJson,strResponse, message->time);
if (ret==0){
message->callBackInThread(message->type,strResponse.c_str());
} else{
message->callBackInThread(-(message->type),strResponse.c_str());
}
LOGI("POST请求结果:%s,返回的值是:%d",strResponse.c_str(),ret);
break;
case 3:
ret= commonTools->download_file(message->url, message->savePath,SendProToJava);
if (ret==0){
message->callBackInThread(message->type,"ret:"+ret);
} else{
message->callBackInThread(-(message->type),"ret:"+ret);
}
LOGI("DOWN请求结果:%s,返回的值是:%d",strResponse.c_str(),ret);
break;
}
int a = 10;
int i = 0;
for (i;i<a;i++){
LOGI("this is %d",i);
sleep(1);
}
return 0;
}
以及头文件:
#include <jni.h>
#include <string>
#include <pthread.h>
#include <android/log.h>
#define TAG "CZ-JNI"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define Http_Get(RETURNTYPE ,FUNCTIONNAME ,...) JNIEXPORT RETURNTYPE JNICALL \
Java_com_amt_pushclient_NativeUtils_##FUNCTIONNAME\
(JNIEnv* env, jobject instance /* this */,##__VA_ARGS__)\
#define Http_Post(RETURNTYPE ,FUNCTIONNAME ,...) JNIEXPORT RETURNTYPE JNICALL \
Java_com_amt_pushclient_NativeUtils_##FUNCTIONNAME\
(JNIEnv* env, jobject instance /* this */,##__VA_ARGS__)\
#define Http_DownLoad(RETURNTYPE ,FUNCTIONNAME ,...) JNIEXPORT RETURNTYPE JNICALL \
Java_com_amt_pushclient_NativeUtils_##FUNCTIONNAME\
(JNIEnv* env, jobject instance /* this */,##__VA_ARGS__)\
pthread_t pid; //子线程
jobject jobject_global; // 全局的 再子线程中回调使用 与Java的那个类持有
JavaVM* javaVM; // 子线程中Jnienv 要单独获取
//定义一个名叫CallBackInThread回调函数指针
typedef void* CallBackInThread(int type, const char *result);
//定义一个请求时候用的结构体
typedef struct Message{
int time;
int type;
char* url;
char* reqJson;
char* savePath;
CallBackInThread* callBackInThread;
} Message;
//回调请求信息给java
void* SendMsgToJava(int type, const char *result) ;
//回调下载进度给java
void* SendProToJava(double progress );
//回调函数当创子建线程的时候
void* callCreateThread( void* args );
对应封装POST GET 以及文件下载的工具类CommonTools.cpp:
/*
* CommonTools.h
* Created on: 2020年4月6日
* Author: chenzhu
*/
#include "CommonTools.h"
CallProgress* CommonTools::callProgress;//定义,分配内存,以后A每一个对象(实例)的创建都不再分配内存
CommonTools::CommonTools(){
LOGI("构造函数");
};
CommonTools::~CommonTools(){
LOGI("析构函数");
};
size_t CommonTools::receive_data(void *contents, size_t size, size_t nmemb, void *stream){
string *str = (string*)stream;
(*str).append((char*)contents, size*nmemb);
return size * nmemb;
}
size_t CommonTools::writedata2file(void *ptr, size_t size, size_t nmemb, FILE *stream) {
size_t written = fwrite(ptr, size, nmemb, stream);
return written;
}
int CommonTools::download_file(const char* url, const char outfilename[FILENAME_MAX],CallProgress* callprogress){
LOGI("地址是: %p",callprogress);
LOGI("地址是: %p",CommonTools::callProgress);
CommonTools::callProgress= callprogress;
LOGI("地址是: %p",CommonTools::callProgress);
CURL *curl;
FILE *fp;
CURLcode res;
/* 调用curl_global_init()初始化libcurl */
res = curl_global_init(CURL_GLOBAL_ALL);
if (CURLE_OK != res)
{
curl_global_cleanup();
return -1;
}
/* 调用curl_easy_init()函数得到 easy interface型指针 */
curl = curl_easy_init();
if (curl) {
fp = fopen(outfilename,"wb");
/* 调用curl_easy_setopt()设置传输选项 */
res = curl_easy_setopt(curl, CURLOPT_URL, url);
if (res != CURLE_OK)
{
fclose(fp);
curl_easy_cleanup(curl);
return -1;
}
/* 根据curl_easy_setopt()设置的传输选项,实现回调函数以完成用户特定任务 */
res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CommonTools::writedata2file);
if (res != CURLE_OK){
fclose(fp);
curl_easy_cleanup(curl);
return -1;
}
/* 根据curl_easy_setopt()设置的传输选项,实现回调函数以完成用户特定任务 */
res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
if (res != CURLE_OK)
{
fclose(fp);
curl_easy_cleanup(curl);
return -1;
}
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, &my_progress_func); //设置回调的进度函数
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, "flag"); //此设置对应上面的const char *flag
res = curl_easy_perform(curl);
// 调用curl_easy_perform()函数完成传输任务
fclose(fp);
/* Check for errors */
if(res != CURLE_OK){
curl_easy_cleanup(curl);
return -1;
}
/* always cleanup */
curl_easy_cleanup(curl);
// 调用curl_easy_cleanup()释放内存
}
curl_global_cleanup();
return 0;
}
CURLcode CommonTools::HttpGet(const string& strUrl, string& strResponse,int nTimeout){
CURLcode res;
CURL* pCURL = curl_easy_init();
if (pCURL == NULL) {
return CURLE_FAILED_INIT;
}
curl_easy_setopt(pCURL, CURLOPT_URL, strUrl.c_str());
//curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(pCURL, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(pCURL, CURLOPT_TIMEOUT, nTimeout);
curl_easy_setopt(pCURL, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(pCURL, CURLOPT_WRITEFUNCTION, CommonTools::receive_data);
curl_easy_setopt(pCURL, CURLOPT_WRITEDATA, (void*)&strResponse);
res = curl_easy_perform(pCURL);
curl_easy_cleanup(pCURL);
return res;
}
CURLcode CommonTools::HttpPost(const string & strUrl, string szJson,string & strResponse,int nTimeout){
CURLcode res;
char szJsonData[1024];
memset(szJsonData, 0, sizeof(szJsonData));
strcpy(szJsonData, szJson.c_str());
CURL* pCURL = curl_easy_init();
struct curl_slist* headers = NULL;
if (pCURL == NULL) {
return CURLE_FAILED_INIT;
}
CURLcode ret;
ret = curl_easy_setopt(pCURL, CURLOPT_URL, strUrl.c_str());
// std::cout << ret << std::endl;
ret = curl_easy_setopt(pCURL, CURLOPT_POST, 1L);
headers = curl_slist_append(headers,"content-type:application/json");
ret = curl_easy_setopt(pCURL, CURLOPT_HTTPHEADER, headers);
ret = curl_easy_setopt(pCURL, CURLOPT_POSTFIELDS, szJsonData);
//curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
ret = curl_easy_setopt(pCURL, CURLOPT_TIMEOUT, nTimeout);
ret = curl_easy_setopt(pCURL, CURLOPT_WRITEFUNCTION, CommonTools::receive_data);
ret = curl_easy_setopt(pCURL, CURLOPT_WRITEDATA, (void*)&strResponse);
res = curl_easy_perform(pCURL);
curl_easy_cleanup(pCURL);
return res;
}
int CommonTools::my_progress_func(char *flag, double t, /* dltotal */ double d, /* dlnow */double ultotal, double ulnow){
LOGI("%s %g / %g (%g %%)\n", flag, d, t, d*100.0/t);
CommonTools::callProgress(d*100.0/t );
return 0;
}
以及对于的头文件:
/*
* CommonTools.h
* Created on: 2020年4月6日
* Author: chenzhu
*/
#ifndef MY_APPLICATION_COMMONTOOLS_H
#define MY_APPLICATION_COMMONTOOLS_H
#include <android/log.h>
#include "zlib.h"
#include "include/curl/curl.h"
#include <vector>
#include <unistd.h>
#include <memory.h>
#include <unistd.h>
#include <string>
using namespace std;
#define LOG_TAG ("chenzhu")
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO , LOG_TAG, __VA_ARGS__))
typedef void* CallProgress(double progress );
//静态变量是属于类的,所以可以通过class::variable访问,
// 非静态变量是属于对象的,需要new,new就需要实现析构函数和构造函数
class CommonTools{
public:
CommonTools();
~CommonTools();
public:
static CallProgress* callProgress ;//这里只是声明,并没有定义,需要再cpp文件中定义分配
static size_t receive_data(void *contents, size_t size, size_t nmemb, void *stream);
// HTTP 下载文件的回掉函数
static size_t writedata2file(void *ptr, size_t size, size_t nmemb, FILE *stream);
// 文件下载接口
int download_file(const char* url, const char outfilename[FILENAME_MAX],CallProgress* callprogress);
// http get 请求
static CURLcode HttpGet(const std::string & strUrl, std::string & strResponse,int nTimeout);
// htpp post 请求
static CURLcode HttpPost(const std::string & strUrl, std::string szJson,std::string & strResponse,int nTimeout);
static int my_progress_func(char *progress_data, double t, /* dltotal */ double d, /* dlnow */ double ultotal, double ulnow);
};
#endif //MY_APPLICATION_COMMONTOOLS_H
这样就完成了在ndk请求的全部。不过需要注意的是。在Java中的回调方法是在子线程中,所以不能进行UI操作。
源码地址:源码