OkHttpClient是目前开发 android 应用使用最广泛的网络框架,最近看了阿里的 httpdns 里面对于 dns 的处理,我们团队就想调研一下在项目中有什么利弊,并且框架中是否对 socket 的连接是怎么缓存的。下面就带着问题去分析一下这个框架:
这个建造者的每个传参在源码分析2中有详细的讲解
- new OkHttpClient.Builder().build();
发送请求的只有这一个方法,就顺着点进去看下是怎么设计的
- @Override public Call newCall(Request request) {
- return RealCall.newRealCall(this, request, false /* for web socket */);
- }
- /**
- *注意这个方法是没有声明 public 的,只能在包内部调用,所以用户无法直接调用.
- *并在这里eventListenerFactory为每个 call 设置了监听
- **/
- static
- RealCall
- newRealCall
- (OkHttpClient client, Request originalRequest, boolean forWebSocket)
- {
- RealCall call = new RealCall(client, originalRequest, forWebSocket);
- call.eventListener = client.eventListenerFactory().create(call);
- return call;
- }
- //私有构造方法,只能通过上面的静态方法调用,这样的好处是,防止框架使用者传参有误导致无法正常使用
- private
- RealCall
- (OkHttpClient client, Request originalRequest, boolean forWebSocket)
- {
- this.client = client;
- this.originalRequest = originalRequest;
- this.forWebSocket = forWebSocket;
- this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
- }
- //request 也是通过构造者生成的, 整个请求的头,行,体都从这里传入,并多了一个 tag 作为标识
- public static class Builder {
- HttpUrl url;
- String method;
- Headers.Builder headers;
- RequestBody body;
- Object tag; //使用者可以设置一个唯一标识,拿到 Request 的时候分不出是哪个请求
- //默认设定请求使用了 GET 方法,并传入空 Headers
- public Builder() {
- this.method = "GET";
- this.headers = new Headers.Builder();
- }
- }
String
- url,
- method
通过方法
传入,使用HttpUrl对 url 封装,方便获取 url 的各个部分信息。可以直接调用
- url()
等设置 methord 并传入 Body,也可以使用
- get(),head(), post(body),delete(),put(body)
直接输字符串,这个方法里进行了校验,传参出错抛出异常
- method(String method,RequestBody body)
Headers.Builder中使用 addLenient,add 添加一行 header,可以是“key:value”这样的字符串,也可以是两个参数 key,value。 调用 add 检测中文或不可见字符会抛出异常,而addLenient 不会检测。 还有一个 set 会覆盖已经存在所有name一样的值
Headers 中用一个数组保存数据,偶数位是 key,奇数位是 value,里面的大部分方法是处理这个数组的。 除了用 buidler 生成header,还可以调用静态方法
传入一个 map 或者[key1,value1,key2,value2...]生成
- of
- String get(String name) //获取 key=name 的 value
- public List<String> values(String name) //和 get 方法类似,如果有多个 key=name 的条目返回所有的 value。(http 协议的header不限制顺序,不排重)
- public Date getDate(String name)//内部调用get(name)并转成时间
- public int size() //header 的条数,保存了key、value数组的一半
- public String name(int index) //获取指定位置的 name,index相对于 header 的条数,不是数组的 index
- public String value(int index)//获取指定位置的value,index相对于 header 的条数,不是数组的 index
- public Set<String> names()//获取所有的 name,由于是 set 重复的只算一条
- public long byteCount() //整个 header 要占用的长度,包含分割的“: ”和回车换行
- public Map<String, List<String>> toMultimap()//把这个 Headers 转成 map,由于可能有多个相同的 key,value 要用 list返回
框架中两个实现类:FormBody上传普通的参数请求,MultipartBody上传文件。RequestBody中三个静态 create 方法传入MediaType和ByteString、byte[]、File 满足了大部分请求需要,并且传给MultipartBody。
如果需要自定义需要至少实现contentType,writeTo,分别传入数据类型和写入。如果有断点续传的要求复写contentLength。
- public
- abstract
- MediaType
- contentType
- ()
- ;
- public
- long
- contentLength
- ()
- throws
- IOException
- {
- return -1;
- }
- public
- abstract
- void
- writeTo
- (BufferedSink sink)
- throws
- IOException
- ;
- /**出来下面三个主要方法的调用还有一些获取 name 和 value 的方法,和 builder 里添加name 和 value的方法,容易理解不做解释。
- **/
- public final
- class
- FormBody
- extends
- RequestBody
- {
- private static final MediaType CONTENT_TYPE = MediaType.parse("application/x-www-form-urlencoded");
- @Override public MediaType contentType() {
- return CONTENT_TYPE;//返回了x-www-form-urlencoded的类型,适用于带参数的接口请求
- }
- @Override
- public
- long
- contentLength
- ()
- {
- return writeOrCountBytes(null, true);//调用了writeOrCountBytes
- }
- @Override
- public
- void
- writeTo
- (BufferedSink sink)
- throws
- IOException
- {
- writeOrCountBytes(sink, false);//调用了writeOrCountBytes
- }
- private
- long
- writeOrCountBytes
- (@Nullable BufferedSink sink, boolean countBytes)
- {
- long byteCount = 0L;
- Buffer buffer;
- if (countBytes) {//只需要获取 body 的长度,contentLength
- buffer = new Buffer();
- } else {
- buffer = sink.buffer();//写入数据 writeTo
- }
- for (int i = 0, size = encodedNames.size(); i < size; i++) {
- if (i > 0) buffer.writeByte('&');//第一个请求参数前不拼'&'
- buffer.writeUtf8(encodedNames.get(i));
- buffer.writeByte('=');//拼接请求体
- buffer.writeUtf8(encodedValues.get(i));
- }
- if (countBytes) {//如果是 contentLength 调用需要返回长度
- byteCount = buffer.size();
- buffer.clear();
- }
- return byteCount;
- }
MultipartBody和FormBody计算 body 长度和写入数据的方法类似,但是MediaType类型比较多,看服务器需要哪种类型的,我们公司服务器是MediaType.parse("multipart/form-data")的,在 builder 中添加了多个part,writeTo的时候用boundary分割开。
- public static final class Builder {
- private final ByteString boundary; //每个 part 的分割线,基本上都是一个够长的随机串
- private MediaType type = MIXED; //默认类型MediaType.parse("multipart/mixed")
- private final List < Part > parts = new ArrayList < >(); //保存了所有的 part
- public Builder(); //随机生成了一个boundary,没有特殊需求不要修改
- public Builder(String boundary); //自定义一个boundary
- public Builder setType(MediaType type); //自定义类型
- public Builder addPart(Headers headers, RequestBody body); //添加一个 part 如果上传文件可以自定义 headers, FormBody.create(MultipartBody.FORM, file)
- public Builder addFormDataPart(String name, String value); //添加一个字符串的 body
- /**添加普通 header 的文件.
- *@params name 是服务器接受 file 的字段名;
- *@params filename 本地的文件名
- *@params body 传FormBody.create(MultipartBody.FORM, new File(filePath))
- **/
- public Builder addFormDataPart(String name, @Nullable String filename, RequestBody body);
使用字段executed防止一个 RealCall请求多次,
未完待续
来源: https://juejin.im/post/5a28dcf751882561a20a5d93