从零撸一个优雅的 Android Http 网络框架(一)框架搭建

从零撸一个优雅的Android Http网络框架系列(一)框架搭建

此系列文章基于Java原生URLConnection打造一款优雅的HTTP网络框架,惊喜多多,学点多多,全面理解Http,做SDK时选择网络框架就有了更多选择

Github地址:https://github.com/huanghaibin-dev/HttpNet

最终的完成效果是这样的:(是不是有点OkHttp的feed?)

Request request = new Request.Builder()
                .encode("UTF-8")
                .method("POST")
                .content(new JsonContent("json字符串")
                .timeout(13000)
                .url("http://xxx.xxxxx.com")
                .build();
HttpNetClient client = new HttpNetClient();
client.setProxy("192.168.1.1",80);//您也可以开启该客户端全局代理
client.newCall(request)
                //如果采用上传文件方式,可以在这里开启上传进度监控
                .intercept(new InterceptListener() {
                    @Override
                    public void onProgress(final int index, final long currentLength, final long totalLength) {
                        Log.e("当前进度", " -- " + ((float) currentLength / totalLength) * 100);
                    }
                })
                .execute(new Callback() {
                    @Override
                    public void onResponse(Response response) {
                        String body = response.getBody();//读取字符串
                        InputStream is = response.toStream();//如果采用下载,可以在这里监听下载进度
                    }

                    @Override
                    public void onFailure(Exception e) {
                        Log.e("onFailure", " onFailure " + e.getMessage());
                    }
                });

码代码之前,我们有必要先来确定一下整体框架,一个好的框架便于我们阅读、理解、迭代,总体如下图:

《从零撸一个优雅的 Android Http 网络框架(一)框架搭建》

1、整体入口就是HttpNetClient,即创建HttpNetClient

2、创建我们的请求对象Request,这里采用静态工厂模式Builder,简单粗暴易懂,Builder由url、method、HttpContent、RequestParams、Header,Proxy等组成

3、由分发器Dispatcher并发执行我们构建的Request,所以分发器是我们创建的线程池

4、HttpContent是我们创建的请求体,所需要的请求内容由RequestParams构建而来,这里有三种:MultiPartContent、JsonContent、FormContent,分别对应POST请求的三种Content-Type:multipart/form-data || application/json; charset=utf-8 || application/x-www-form-urlencoded

5、准备就绪开始连接服务器:Connection,这里就两种:HttpConnection、HttpsConnection https需要考虑自签名证书、双向SSL验证等安全问题,当然这些都没什么压力

6、Response得到服务器的回掉,这里大有文章,我们要考虑InputStream关闭的问题、下载大文件的问题、处理各种balabala一大堆,好比AsyncHttpClient如果想下载大文件是比较麻烦的,这里我们采用类似OkHttp的方案: Response.close();

7、Callback是我们的异步回掉,当然我们可以选择同步方法,方便结合RxJava使用。

开始我们的代码之旅

构建我们的请求Request

public final class Request {
    private String mUrl;//url
    private String mMethod;//请求方法
    private RequestParams mParams;//请求参数
    private Headers mHeaders;//头部
    private String mEncode;//编码
    private int mTimeout;//超时
    private HttpContent mHttpContent;//http请求体
    private String mHost;//代理host
    private int mPort;//代理端口

    private Request(Builder builder) {
        this.mUrl = builder.mUrl;
        this.mHeaders = builder.mHeaders.build();
        this.mMethod = builder.mMethod;
        this.mParams = builder.mParams;
        this.mHttpContent = builder.mHttpContent;
        this.mEncode = builder.mEncode;
        this.mTimeout = builder.mTimeout;
        this.mHost = builder.mHost;
        this.mPort = builder.mPort;
    }

    public String url() {
        return mUrl;
    }

    public String method() {
        return mMethod;
    }

    public RequestParams params() {
        return mParams;
    }

    public Headers headers() {
        return mHeaders;
    }

    public HttpContent content() {
        return this.mHttpContent;
    }

    public String encode() {
        return mEncode;
    }

    public int timeout() {
        return mTimeout;
    }

    public String host() {
        return mHost;
    }

    public int port() {
        return mPort;
    }

    public static final class Builder {
        private String mUrl;
        private String mMethod;
        private String mEncode;
        private int mTimeout;
        private RequestParams mParams;
        private Headers.Builder mHeaders;
        private HttpContent mHttpContent;
        private String mHost;
        private int mPort;

        public Builder() {
            this.mMethod = "GET";
            this.mEncode = "UTF-8";
            this.mTimeout = 13000;
            this.mHeaders = new Headers.Builder();
        }

        public Builder url(String url) {
            if (url == null) throw new NullPointerException("url can not be null");
            this.mUrl = url;
            return this;
        }

        public Builder encode(String encode) {
            if (encode == null) throw new NullPointerException("encode can not be null");
            this.mEncode = encode;
            return this;
        }

        public Builder timeout(int timeout) {
            this.mTimeout = timeout;
            if (mTimeout <= 0) mTimeout = 13000;
            return this;
        }

        public Builder method(String method) {
            if (method == null) throw new NullPointerException("method can not be null");
            this.mMethod = method;
            return this;
        }

        public Builder params(RequestParams params) {
            if (params == null) throw new NullPointerException("params can not be null");
            this.mParams = params;
            return this;
        }

        public Builder headers(Headers.Builder headers) {
            this.mHeaders = headers;
            return this;
        }

        public Builder content(HttpContent content) {
            if (content == null) throw new NullPointerException("content can not be null");
            this.mHttpContent = content;
            return this;
        }

        public Builder proxy(String host, int port) {
            if (host == null) throw new NullPointerException("host can not be null");
            this.mHost = host;
            this.mPort = port;
            return this;
        }

        public Request build() {
            if (mHttpContent == null && mParams != null) {
                if (mParams.getMultiParams() != null) {
                    mHttpContent = new MultiPartContent(mParams, mEncode);
                } else {
                    mHttpContent = new FormContent(mParams, mEncode);
                }
            }
            return new Request(this);
        }
    }
}

看完请求Request简单粗暴有没有?接下来构建我们需要的请求参数,这里需要注意很特殊的需要:form表单有多个相同的key,所以我们采用IdentityHashMap这个特殊的HashMap,他的Key是一个对象地址,我们采用静态内部类Key包装String达到我们的需求

public final class RequestParams {
    private IdentityHashMap<Key, String> textParams;//表单
    private IdentityHashMap<Key, File> multiParams;//文件

    public RequestParams() {
        textParams = new IdentityHashMap<>();
    }

    public RequestParams put(String name, String value) {
        textParams.put(new Key(name), value);
        return this;
    }

    public RequestParams put(String name, int value) {
        return put(name, String.valueOf(value));
    }

    public RequestParams put(String name, long value) {
        return put(name, String.valueOf(value));
    }

    public RequestParams put(String name, double value) {
        return put(name, String.valueOf(value));
    }

    public RequestParams put(String name, float value) {
        return put(name, String.valueOf(value));
    }

    public RequestParams put(String name, byte value) {
        return put(name, String.valueOf(value));
    }

    public RequestParams put(String name, boolean value) {
        return put(name, String.valueOf(value));
    }

    public RequestParams putFile(String name, File file) {
        if (multiParams == null) multiParams = new IdentityHashMap<>();
        if (!file.exists())
            return this;
        multiParams.put(new Key(name), file);
        return this;
    }

    public RequestParams putFile(String name, String fileName) {
        return putFile(name, new File(fileName));
    }

    public IdentityHashMap<Key, String> getTextParams() {
        return textParams;
    }

    public IdentityHashMap<Key, File> getMultiParams() {
        return multiParams;
    }


     /** * 静态内部类包装Key */
    public static class Key {
        private String name;

        public Key(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

接下来我们继续封装Headers,这里也需要注意Headers是可以有多个相同的key的,我们采用HttpUrlConnection.getHeaderFields();相同的Map

public class Headers {
    private Map<String, List<String>> mHeaders;

    private Headers(Builder builder) {
        this.mHeaders = builder.mHeaders;
    }

    public Map<String, List<String>> getHeaders() {
        return mHeaders;
    }

    public void setHeaders(Map<String, List<String>> mHeaders) {
        this.mHeaders = mHeaders;
    }

    public static final class Builder {
        private Map<String, List<String>> mHeaders;

        public Builder() {
            mHeaders = new HashMap<>();
        }

        public Builder addHeader(String name, String value) {
            checkNotNull(name, value);
            if (mHeaders.containsKey(name)) {
                if (mHeaders.get(name) == null) mHeaders.put(value, new ArrayList<String>());
                mHeaders.get(name).add(value);
            } else {
                List<String> h = new ArrayList<>();
                h.add(value);
                mHeaders.put(name, h);
            }
            return this;
        }

        public Builder setHeader(String name, String value) {
            if (mHeaders.containsKey(name)) {
                mHeaders.remove(name);
            }
            List<String> h = new ArrayList<>();
            h.add(value);
            mHeaders.put(name, h);
            return this;
        }

        private void checkNotNull(String name, String value) {
            if (name == null) throw new NullPointerException("name can not be null");
            if (value == null) throw new NullPointerException("value can not be null");
        }

        public Headers build() {
            return new Headers(this);
        }
    }
}

基础框架搭建就到此结束,下一篇全面讲解HttpContent,全面理解HTTP是怎么发送的数据和报文格式

    原文作者:HTTP
    原文地址: https://juejin.im/entry/58e5f01d0ce463005849670d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞