基于libcurl、openssl的跨平台网络库的封装--Android篇

前言

    这篇文章中会介绍如何在Android平台用curl+openssl作为网络库进行native封装和java封装成为一套简单易用的http网络库。之所以可以称为“跨平台”,是因为curl、openssl以及c++的功能封装和线程池等基础功能都是完全跨平台的代码,只要编译成对应平台的库即可实现底层功能的跨平台。上层调用层则需要再封装,而本篇文章所用的平台是Android平台,所以上层封装调用是java代码,这一部分是无法跨平台的,需要在各个平台分别实现。

结构总览

    直接看一下各个功能的总览图。


基于libcurl、openssl的跨平台网络库的封装--Android篇_第1张图片
androidcurl.png

    接下来将介绍从基础库的交叉编译到最终demo完成的各个环节。

交叉编译基础库

    首先从最基本的功能开始,要使用curl就需要将curl代码编译成为目标平台的库,同时可能我们编写native功能的时候可能需要在pc平台开发调试,这样的效率会比较高,所以至少需要编译一个pc库(mac或windows)和android需要的库。
    首先看一下编译mac使用的库的编译过程(使用mac开发c++封装部分)。第一步需要下载curl的源代码https://github.com/curl/curl,如果需要历史稳定版可以这里找到https://curl.haxx.se/download/。第二步,如果之前没有编译过可能mac会缺少一些自动生成工具--m4、libtool、automake、autoconf。你可以通过下面的信息下载安装它们:
1)安装m4

http://ftp.gnu.org/gnu/m4/
tar -xzvf m4-1.4.17.tar.gz
cd m4-1.4.17
./configure --prefix=/usr/local
make
sudo make install

2)安装autoconf

http://ftp.gnu.org/gnu/autoconf/
tar -xzvf autoconf-2.69.tar.gz
cd autoconf-2.69
./configure --prefix=/usr/local
make
sudo make install

3)安装automake

http://ftp.gnu.org/gnu/automake/
tar xzvf automake-1.15.tar.gz
cd automake-1.15
./configure --prefix=/usr/local
make
sudo make install

4)安装libtool

http://mirror.hust.edu.cn/gnu/libtool/
tar xzvf libtool-2.4.6.tar.gz
cd libtool-2.4.6
./configure --prefix=/usr/local
make
sudo make install

第三步,编译curl--在curl源码路径下./buildconf
./configure --prefix=./libcurl --with-darwinssl
make
make install
运行之后会在libcurl目录下找到编译产物,我们可以用这些库在mac上进行功能开发和调试。
    接下来看一下编译android上用的库(上面的4个基础库下载安装是同样需要的),这里就要提到openssl了,为什么之前mac版本不需要编译openssl呢?因为mac的framework中有自己的ssl实现(darwinssl)。在android中虽然也有系统层的ssl实现(目前google用的是openssl的分支boringssl),但是我们仍然需要将openssl编译到curl中以提供TLS的能力。交叉编译android需要的库是一件很繁琐的事情,主要是ndk的不同版本会导致编译失败,所以这里没必要浪费时间,直接用我编译好的库或者用我写好的跑通的编译脚本吧:https://github.com/yutianzuo/build_script,在这里有直接可以用的编译产物,也有编译脚本,和编译的源码版本,用的ndk在脚本中有标注。至此基础工作准备完成,下面看代码。

C++封装部分代码解析

    依旧从整体结构看一下,在上到下三张类图可以代码c++功能的全部。


基于libcurl、openssl的跨平台网络库的封装--Android篇_第2张图片
curl_request.png
基于libcurl、openssl的跨平台网络库的封装--Android篇_第3张图片
curl_manager.png
基于libcurl、openssl的跨平台网络库的封装--Android篇_第4张图片
curl_httpmanager.png

    以上三张图,从上到下以此是被封装和封装的关系。
    HttpRequest是最基础的封装类,它封装了curl的c API实现了最最基本的http功能,包括设置hosturl,设置header,设置代理地址,设置证书路径等等,这些都是每一个http请求需要的基础功能。由此派生出各种业务可能需要的http请求类--get、postform,postfile、download、put等等,每一个子类实现特定的http请求类型,在子类中处理更上一层的功能。
    RequestManager是对所有类型的http请求的封装,它可以作为一类业务功能的集合,比如都要访问hostA的请求都在此类中处理,它管理这所有类型的请求。它同时管理着线程池,目前的实现是多个RequestManager示例共用一个线程池。
    HttpManager作为一个单例是最上层封装,它管理多个RequestManager实例,并且管理着一个全局锁以供业务方使用。
    那么C++部分的功能代码就是这样的,它的调用看起来是这个样子

{
    HttpManager::init(5);


    RequestManager::STRING_MAP headers;
    headers["1"] = "2";
    headers["3"] = "4";
    RequestManager::STRING_MAP params;
    params["5"] = "6";
    params["7"] = "8";

    RequestManager *manager_download = HttpManager::get_instance()->get_request_manager("hashcode");
    manager_download->set_proxy_path("http://localhost:8888");//for debug
    manager_download->set_host(
            "path/somefile.file");
    manager_download->download("", headers, params, "/Users/yourname/Downloads/test.file",
                               [](int
                                  result,
                                  const std::string &str_respones,
                                  float persent,
                                  size_t call_back_seq,
                                  int internal_code,
                                  void *extra)
                               {}, 12
    );

    //sleep for callback...

    HttpManager::uninit();
}

Android上层调用封装

    C++部分代码直接移植到android studio上就可以编译通过,只不过我们需要配置一下编译脚本,这里用到AS3.1版本,编译脚本为CMake方式。具体的工程配置和脚本配置可见源码。这里需要提示一下,底层库只编译了v7a版本,所以aar层制定了abi类型只接受v7a,这是在实践中总结的比较稳妥的方式,一来减少包体积,二来减少了粗心少提供不同cpu架构库的崩溃几率。
    首先看一下jni层的封装

public class JniCurl {
    static {
        System.loadLibrary("native_net");
    }

    public static native void init(int threadPoolSize, Object callBack);

    public static native void unInit();

    public static native void addBasicHeader(String strHash, String strKey, String strValue);

    public static native void addBasicURLParam(String strHash, String strKey, String strValue);

    public static native void setHost(String strHash, String strHost);

    public static native void setCertPath(String strHash, String strCertPath);

    public static native void setProxy(String strHash, String proxy);

    public static native void get(String strHash, int requestSeq, String strPath, List headers_keys,
            List headers_values, List params_keys, List params_values);

    public static native void postFromData(String strHash, int requestSeq, String strPath, List headers_keys,
            List headers_values, List params_keys, List params_values);

    public static native void postJson(String strHash, int requestSeq, String strPath, List headers_keys,
            List headers_values, String strJson);

    public static native void putJson(String strHash, int requestSeq, String strPath, List headers_keys,
            List headers_values, String strJson);

    public static native void postFile(String strHash, int requestSeq, String strPath, List headers_keys,
            List headers_values, String strFormName, List params_keys, List params_values,
            String strJsonName, String strJson, String strFileKeyName, String strFilePath, String strFileName);

    public static native void download(String strHash, int requestSeq, String strPath, List headers_keys,
            List headers_values, List params_keys, List params_values, String strFilePath);
}

    这里基本就是对C++层的RequestManager能力的一个封装,我们再通过aar层android库的封装起到对这些基本功能的一个管理目的。aar层的封装基本和C++功能层的封装是对应的,同样是RequestManager和HttpManager两层来进行功能封装,这里不赘述了。不同的是java的HttpManager承担了native回调分发的工作,在这里来沟通上下的数据传递。还有一点值得注意的是java和C++的RequestManager之间的对应关系是通过上层的一个hash值来对应起来的,也就是说java层去调用具体功能都是用自己保有的一个hash值去操作底层的对象,在上层看来这个hash值就是底层的native对象。具体代码可以参见工程代码。
    具体java功能也具备了,那么接下来就是上层调用功能了,比如回调回传的数据还是一个utf-8字符串,反序列化解析的一些功能就需要业务层来实现了,另外还有就是调用的便捷性也需要业务层来实现了,这里直接贴一下我简单封装的业务层代码。具体工程可以依照这个思路封装。


public enum BizNetWrapper {
    INSTANCE;
    RequestManager mRequest1;
    RequestManager mRequest2;
    RequestManager mRequest3;


    public void init(Context context) {
        HttpManager.INSTANCE.Uninit();
        HttpManager.INSTANCE.Init(5, context);
    }

    public void uninit() {
        HttpManager.INSTANCE.Uninit();
        mRequest1 = null;
        mRequest2 = null;
        mRequest3 = null;
    }

    public RequestManager getBizRequestManager(Context context) {
        if (mRequest1 == null) {
            mRequest1 = HttpManager.INSTANCE.getRequest();
            mRequest1.setHost("http://news.163.com/");
            mRequest1.addBasicHeader("MyCookie", "123456789");
            mRequest1.addBasicHeader("MyCookie2", "123456789123456");
            mRequest1.addBasicUrlParams("param1", "value");
            mRequest1.addBasicUrlParams("param2", "value");
            mRequest1.setCertPath(Misc.getAppDir(context) + Misc.CERT_NAME);
//            mRequest1.setProxy("http://172.18.100.56:8888"); //for debug
        }
        return mRequest1;
    }

    public RequestManager getBizRequestManager2(Context context) {
        if (mRequest2 == null) {
            mRequest2 = HttpManager.INSTANCE.getRequest();
            mRequest2.setHost("http://example.com/");
            mRequest2.setCertPath(Misc.getAppDir(context) + Misc.CERT_NAME);
//            mRequest2.setProxy("http://172.18.100.56:8888"); //for debug
        }
        return mRequest2;
    }

    public RequestManager getBizRequestManager3(Context context) {
        if (mRequest3 == null) {
            mRequest3 = HttpManager.INSTANCE.getRequest();
            mRequest3.setHost("https://somehost");
            mRequest3.setCertPath(Misc.getAppDir(context) + Misc.CERT_NAME);
//            mRequest3.setProxy("http://172.18.100.56:8888"); //for debug
        }
        return mRequest3;
    }

    public interface HttpCallbackBiz {
        void success(BeanTest data);

        void fail(int errcode);

        void progress(float persent);
    }

    static public class UrlBuilder {
        private RequestManager request;
        private String mPath = ""; //important
        private String mJson;
        private Map headers = new HashMap<>();
        private Map urlParams = new HashMap<>();
        private Map formDataParams = new HashMap<>();

        private String mJsonName;
        private String mFormName;
        private String mFileKeyName;
        private String mFileName;
        private String mFilePath;
        private String mDownFilePath;

        public UrlBuilder with(RequestManager request) {
            this.request = request;
            return this;
        }

        public UrlBuilder addHeader(String key, String value) {
            headers.put(key, value);
            return this;
        }

        public UrlBuilder addUrlParam(String key, String value) {
            urlParams.put(key, value);
            return this;
        }

        public UrlBuilder setPath(String strPath) {
            mPath = strPath;
            return this;
        }

        public UrlBuilder addFormData(String key, String value) {
            formDataParams.put(key, value);
            return this;
        }

        public UrlBuilder setJson(String strJson) {
            this.mJson = strJson;
            return this;
        }

        public UrlBuilder setFormName(String formName) {
            mFormName = formName;
            return this;
        }

        public UrlBuilder setJsonName(String jsonName) {
            mJsonName = jsonName;
            return this;
        }

        public UrlBuilder setFileKeyName(String fileKeyName) {
            mFileKeyName = fileKeyName;
            return this;
        }

        public UrlBuilder setFileName(String fileName) {
            mFileName = fileName;
            return this;
        }

        public UrlBuilder setFilePath(String filePath) {
            mFilePath = filePath;
            return this;
        }

        public UrlBuilder setDownloadFilePath(String filePath) {
            mDownFilePath = filePath;
            return this;
        }

        public void get(final HttpCallbackBiz callback) {
            request.get(mPath, headers, urlParams, new HttpCallback() {
                @Override
                public void success(String respones) {
                    //gson thing...
                    BeanTest bean = new BeanTest();
                    bean.rep = respones;
                    callback.success(bean);
                }

                @Override
                public void progress(float persent) {
                    callback.progress(persent);
                }

                @Override
                public void fail(int errcode) {
                    callback.fail(errcode);
                }
            });
        }

        public void postFormdata(final HttpCallbackBiz callbackBiz) {
            request.postForm(mPath, headers, formDataParams, new HttpCallback() {
                @Override
                public void success(String respones) {
                    //gson thing...
                    BeanTest bean = new BeanTest();
                    bean.rep = respones;
                    callbackBiz.success(bean);
                }

                @Override
                public void progress(float persent) {
                    callbackBiz.progress(persent);
                }

                @Override
                public void fail(int errcode) {
                    callbackBiz.fail(errcode);
                }
            });
        }

        public void postJson(final HttpCallbackBiz callbackBiz) {
            request.postJson(mPath, headers, mJson, new HttpCallback() {
                @Override
                public void success(String respones) {
                    //gson thing...
                    BeanTest bean = new BeanTest();
                    bean.rep = respones;
                    callbackBiz.success(bean);
                }

                @Override
                public void progress(float persent) {
                    callbackBiz.progress(persent);
                }

                @Override
                public void fail(int errcode) {
                    callbackBiz.fail(errcode);
                }
            });
        }

        public void putJson(final HttpCallbackBiz callbackBiz) {
            request.putJson(mPath, headers, mJson, new HttpCallback() {
                @Override
                public void success(String respones) {
                    //gson thing...
                    BeanTest bean = new BeanTest();
                    bean.rep = respones;
                    callbackBiz.success(bean);
                }

                @Override
                public void progress(float persent) {
                    callbackBiz.progress(persent);
                }

                @Override
                public void fail(int errcode) {
                    callbackBiz.fail(errcode);
                }
            });
        }

        public void postFile(final HttpCallbackBiz callbackBiz) {
            request.postFile(mPath, headers, mFormName, formDataParams, mJsonName, mJson, mFileKeyName, mFilePath,
                    mFileName, new
                            HttpCallback() {
                                @Override
                                public void success(String respones) {
                                    //gson thing...
                                    BeanTest bean = new BeanTest();
                                    bean.rep = respones;
                                    callbackBiz.success(bean);
                                }

                                @Override
                                public void progress(float persent) {
                                    callbackBiz.progress(persent);
                                }

                                @Override
                                public void fail(int errcode) {
                                    callbackBiz.fail(errcode);
                                }
                            });
        }

        public void downloadFile(final HttpCallbackBiz callbackBiz) {
            request.downloadFile(mPath, headers, urlParams, mDownFilePath,
                    new
                            HttpCallback() {
                                @Override
                                public void success(String respones) {
                                    //gson thing...
                                    BeanTest bean = new BeanTest();
                                    bean.rep = respones;
                                    callbackBiz.success(bean);
                                }

                                @Override
                                public void progress(float persent) {
                                    callbackBiz.progress(persent);
                                }

                                @Override
                                public void fail(int errcode) {
                                    callbackBiz.fail(errcode);
                                }
                            });
        }

    }

}

调用:

        new BizNetWrapper.UrlBuilder().
                with(BizNetWrapper.INSTANCE.getBizRequestManager(MainActivity.this)).
                addHeader("customheader1", "value").
                addHeader("customheader2", "value").
                addUrlParam("customparam1", "value").
                addUrlParam("customparam2", "value").
                setPath("").
                get(new HttpCallbackBiz() {
                    @Override
                    public void success(BeanTest data) {
                        mTextView.setText("Get TestCase OK");
                        Log.e("JAVA_TAG", data.rep);
                    }

                    @Override
                    public void fail(int errcode) {
                        mTextView.setText("Get TestCase Failed:" + errcode);
                        Log.e("JAVA_TAG", "fail:" + errcode);
                    }

                    @Override
                    public void progress(float persent) {
                        Log.e("JAVA_TAG", "progress");
                    }
                });

最后

    上面简要的大体介绍了一下功能和封装等结构,更多的细节请见源码:https://github.com/yutianzuo/android-curl

你可能感兴趣的:(基于libcurl、openssl的跨平台网络库的封装--Android篇)