从零撸一个优雅的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());
}
});
码代码之前,我们有必要先来确定一下整体框架,一个好的框架便于我们阅读、理解、迭代,总体如下图:
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);
}
}
}