通过NDK使用c++库文件在子线程中网络请求以及文件下载

 

 

1.有时候需要对自己的网络请求数据逻辑进行加密

2.在cpp文件中创建子线程

3.子线程中回调原生方法

 

准备工作:

 需要能支持cmake的Androidstudio版本

编译下载curl,如果没有可以直接使用我的文件,这里编译下载就不多说了。

----------------------------------2021年1月20日更新----------------------------------------------------------

有些小伙伴说需要支持arm64位的,在github 上我已经更新版本 支持各种架构。

并且舍弃静态a库改为动态so库,并且上传源码。小伙伴可以按需编译。

android 系统安装curl 安卓执行curl_android 系统安装curl

这里可以选择构建版本

然后通过

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操作。

 

源码地址:源码