网络库源码
网络框架比较
- 在Android 2.3版本及以后,HttpClientHttpURLConnection则是最佳的选择,HttpURLConnection的API提供的比较简单,可以更加容易地去使用和扩展它。而且速度快、节省电量。内部也改成了OkHttp
- OkHttp 处理了很多网络问题:自动重连、会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题、SSL握手失败问题、自签名。
- volley的设计目标就是非常适合数据量小,通信量大的客户端,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。Volley停止了更新,而OkHttp得到了官方的认可,并在不断优化。因此我最终替换为了OkHttp
- 如何取消一个网络请求call.cancel();(okhttp),在activity的ondestory里取消网络请求
okHttp
优势
大部分库都有,自动智能请求重试 ;持久化 cookie 存储
1.高性能Http请求库。
2.支持SPDY,共享同一个Socket来处理同一个服务器所有的请求.
3.支持http2.0、websocket;支持同步、异步。
4.内部封装了线程池、数据转换、参数使用、错误处理等。
5.支持GZIP来减少数据流量。
6.基于NIO和OKio,性能更高。
OkHttp 处理了很多网络问题:自动重连、会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题、SSL握手失败问题、自签名
HTTP 1. x 中,如果想并发多个请求,必须使用多个 TCP 链接
用法
1 |
|
源码
peiniwan/okhttpTest: okhttp源码解析注释
先构建request对象,然后 okhttp3.Response response = client.newCall(request).execute();
总结:
- 在 newCall (Request request) (request 是请求参数和 URL)的时候,其实是里面创建了一个 RealCall 的对象,里面有 execute () 方法。里面有 getResponseWithInterceptorChain () ,添加了很多 Interceptor,并返回 Response 对象的。
- RealCall.enqueue () 被调⽤的时候⼤同⼩异,区别在于
enqueue () 会使⽤ Dispatcher 的线程池来把请求放在后台线程进⾏(把当前的请求Call.enqueue添加(AsyncCall)到请求队列中,并通过回调(Callback)的方式来获取最后结果
),但实质上使⽤的同样也是 getResponseWithInterceptorChain () ⽅法。 - getResponseWithInterceptorChain () ⽅法做的事:把所有配置好的 Interceptor 放在⼀个 List ⾥,然后作为参数,创建⼀个 RealInterceptorChain 对象,并调⽤ chain.proceed (request) 来发起请求和获取响应。
- RetryAndFollowlnterceptor:负责失败重试、重定向
- Bridgelnterceptor 负责向服务器发送请求数据,例如头消息、cookie 等等
- Cachelnterceptor: 读取缓存、更新缓存
- Connectlnterceptor:负责和服务器建立连接的。在这⾥,OkHttp 会创建出⽹络请求所需要的 TCP 连接(如果是 HTTP),或者是建⽴在 TCP 连接之上的 TLS 连接(如果是 HTTPS),并且会创建出对应的 HttpCodec 对象(⽤于编码解码 HTTP 请求)
- Networklnterceptor:从服务器读取响应数据,数据流拦截器,io 操作,报文封装和解析。
- 每一个功能都只是一个 Interceptor,它们再连接成一个 Interceptor. Chain,最终完成一次网络请求。
- 责任链模式,工厂模式,建造者模式
其他类
- dispatcher :调度器,⽤于调度多线程发起⽹络请求。默认最大并发数 64,单域名最大并发 5。里面用到了线程池和队列,线程池核心数是0。可运行线程数通过队列的个数限制的(ArrayDeque<AsyncCall>),AsyncCall 是个 runable
1 |
|
该线程池的核心线程数为 0,线程池最有能纳 Integer. MAX_VALUE 个线程,且线程的空闲存活时间为 60s(可以理解为 okhttp 随时可以创建新的线程来满足需要。可以保证网络的 I/O 任务有线程来处理,不被阻塞。
1 |
|
2. List<Protocol> protocols :⽀持的应⽤层协议,即 HTTP/1.1、HTTP/2 等
3. Cache cache :Cache 存储的配置。默认是没有,如果需要⽤,得⾃⼰配置出 Cache 存储的⽂件位置以及存储空间上限。
4. boolean followRedirects :遇到重定向的要求是,是否⾃动 follow。根据响应码判断是否是重定向(3开头3xx)。RetryAndFollowUpInterceptor:如果不需要重定向,那么 followUp 为空,会释放资源,返回 response。若为重定向就销毁旧连接,创建新连接,将重定向操作得到的新请求设置给 request。统计重定向次数,不能大于20
5. 连接、读取、写入超时
6. ConnectionPool :连接池默认是可以保持5个空闲的连接。这些空闲的连接如果超过5分钟不被使用,则将被连接池移除。
Exchange ,单个 Http 请求,传输交换数据实现类,
封装上面结果,为后续网络读写工作提供 API
ExchangeFinder ,请求连接获取,请求编解码实例构建
ExchangeCodec,Http 连接 I/O 操作上层封装类,实际 I/O 工作逻辑
前置
response = realChain.proceed (request) 中置,传给下一下
后置
java. net 包下
最后也是 socket:socket.connect (address, connectTimeout)
其他
复用机制
Http 中添加了一种 KeepAlive 机制,当数据传输完毕后仍然保持连接,等待下一次请求时直接复用该连接。
ConnectionPool :取到的话复用,没有取到放到连接池中。
ConnectionPool 关键代码:
okhttp 重试逻辑
1 |
|
addInterceptor 和 addNetworkInterceptor
addNetInterceptor 是添加网络拦截器,addInterceptor 是添加应用拦截器,如果看到 okhttp 的流程分析的知道:应用拦截器 addInterceptor 是在网络拦截器前执行的。
addInterceptor(应用拦截器)在前:
- 不需要担心中间过程的响应,如重定向和重试.
- 总是只调用一次,即使HTTP响应是从缓存中获取.
原始数据,⽽不是没有加 Content-Length 的请求数据,或者 Body还没有被 gzip 解压的响应数据 - 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
- 允许短路而不调用 Chain.proceed(),即中止调用
- 允许重试,使 Chain.proceed()调用多次
刚开始的时候
addNetworkInterceptor(网络拦截器):
- 能够操作中间过程的响应,如重定向和重试执行多次
- 当网络短路而返回缓存响应时不被调用.
- 只观察在网络上传输的数据.
- 携带请求来访问连接
最后:做网络调试
开发者使⽤ addNetworkInterceptor(Interceptor) 所设置的,它们的⾏为逻辑和使⽤ addInterceptor(Interceptor) 创建的⼀样,但由于位置不同,所以这⾥创建的 Interceptor 会看到每个请求和响应的数据(包括重定向以及重试的⼀些中间请求和响应),并且看到的是完整原始数据,⽽不是没有加 Content-Length 的请求数据,或者 Body还没有被 gzip 解压的响应数据。多数情况,这个⽅法不需要被使⽤;
addInterceptor()和 addNetworklnterceptor()的区别是,通过 addInterceptor(添加的 interceptor是为
了业务,而通过 addNetworkInterceptor0)添加的 Interceptor 是为了网络调试。
具体在代码执行上,addNetworkInterceptor拦截到的是未经过处理的 HTTP 信息,例如如果网络响应对数据使用了 Gzip 压缩,你用 addNetworkInterceptors 添加的拦截器,拿到的 Response里是未解压的数据,而
addInterceptors)拿到的是解压后的数据。
自定义一个拦截器
addNetworkInterceptor
1 |
|
缓存
Okhttp 是通过 CacheInterceptor 进行 Cache,它负责网络交互相关。它里面包含了一些复杂的 CRUD逻辑.我们只需要知道如何进行 http 参数配置 ,何时禁止网络,只使用缓存,什么时候忽略网络数据,他的核心还是通过 DiskLruCache 实现了缓存在磁盘中的 LRU 存储,然后通过 Cache-Control 进行更好的 https 协议的缓存的 header。
OKHTTP一般控制缓存有两种方式:
1、在request里面去设置cacheControl()策略
2、在header里面去添加cache-control
Android okhttp缓存真正正确的实现方式
有网时不缓存,没网时设置缓存,通过Cache-Control和max-age
1 |
|
整体流程
Retrofit 原理
用法
1 |
|
源码
- Retrofit 2.0底层依赖OkHttp实现,也就是说Retrofit本质上就是对OkHttp的更进一步封装,还支持Rxjava。Retrofit和其它Http库最大区别在于通过大范围使用注解简化Http请求(请求方式、请求参数)
- 网络请求的工作本质上是OkHttp完成,而 Retrofit 仅负责网络请求接口的封装。
- Retrofit 主要是在 create 方法中采用动态代理模式实现接口方法,这个过程构建了一个 ServiceMethod 对象。(扩展 apiservice 的功能)
1 |
|
这里使用了 invoke
方法是因为 ServiceMethod
类实现了 java.lang.reflect.InvocationHandler
接口,可以通过反射机制动态地调用方法。
loadServiceMethod (method)方法:通过 Method.getAnnotations (); 是获取方法上面对应的注解。method.getParameterAnnotations (),解析注解获取请求方式,参数类型和参数注解拼接请求的链接,当一切都准备好之后会把数据添加到 Retrofit 的 RequestBuilder 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19private ServiceMethod<?> loadServiceMethod(Method method) {
// 获取方法上的注解
Annotation[] annotations = method.getAnnotations();
// 创建 ServiceMethod.Builder 实例
ServiceMethod.Builder<?> builder = new ServiceMethod.Builder<>(this, method);
// 解析方法上的注解,构建 ServiceMethod.Builder 对象
builder.parseAnnotations(annotations);
// 解析方法的返回类型,获取适当的 CallAdapter 对象
CallAdapter<?> callAdapter = builder.createCallAdapter();
// 解析方法的返回类型,获取对应的 Type 对象
Type responseType = builder.parseResponseType();
// 构建 ServiceMethod 对象
return builder.build(callAdapter, responseType);
}然后当我们主动发起网络请求的时候会调用okhttp发起网络请求,okhttp的配置包括请求方式,URL等在Retrofit的RequestBuilder的build()方法中实现,并发起真正的网络请求。
1
new OkHttpCall<>(requestFactory, args, callFactory,responseConverter)
其实Retrofit内部是通过动态代理,内部是通过反射实现的,大家都知道反射其实是很消耗性能的,为了避免这种高性能的消耗,反射成功以后就缓存起来,下次如果有用到就重用,如果不能重用则用反射构建出来,但是Builder是不能重用的。
1
2
3
4
5
6
7
8
9
10
11
12
13Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
netApi repo = retrofit.create(netApi.class);
retrofit2.Call<ResponseBody> call = repo.contributorsBySimpleGetCall("userName", "path");
call.enqueue(new retrofit2.Callback<ResponseBody>() {
}
Method.getAnnotations(); 是获取方法上面对应的注解。
method.getParameterAnnotations();获取的是方法参数上面的注解,是一个二维数组,第一个维度代表的是方法参数对应的下标,比如,一个方法有3个参数,那0代表第一个参数,1代表第二个参数,2代表第三个参数。
method.getGenericParameterTypes();获取的是方法参数的类型,里面带有实际的参数类型。
1.Retrofit构建过程
建造者模式、工厂方法模式
2.创建网络请求接口实例过程
外观模式、代理模式、单例模式、策略模式、装饰模式(建造者模式)
3.生成并执行请求过程
适配器模式(代理模式、装饰模式)
自己写网络请求框架
- volley,okHttp等,这类优秀的框架其底层的实现大部分也是基于系统的 线程池 和 httpClient 或 HttpUrlConnection的网络请求类框架,Android中是不能在主线程中(又称UI线程)进行网络操作的,那么框架中必不可少地要使用到子线程,可以使用简单的 Thread + Runnable + Handler或者重量级点的AsyncTask。
- 处理好并发操作,一个应用中往往要进行多线程操作,而Java虚拟机对于一个线程的内存分配大约在1M左右,具体多少要看它执行的任务而定。所有就要使用线程池,例如newFixdThreadPool 可以控制并发数量,且在整个APP运行过程中有几个常驻线程在,避免使用时反复地new,退出时再销毁,而 newCacheThreadPool 则会在任务完成后,自动回收线程,它会帮你释放线程内存,也就不会有常驻线程。
- 还要注意使接口分离,降低耦合,而且接口能够我们带来很大的方便。
应用中的网络层是怎么设计的
用现成的一些框架,然后根据项目需要自己再封装下,比如说你的交互数据是JSON格式的,你就可以用一个网络请求框架+gson,然后写一些Bean 在Work(主)线程把数据用gson直接解析成对象返回,最后对一些错误统一处理。我用的是(volley),封装了下常用的方法,get post 上传 下载 ,所有的请求我都是用的同步请求. 具体的用法一般都是和业务逻辑在一起,而我的业务逻辑是用异步去处理的
volley
为什么说Volley适合数据量小,通信频繁的网络操作
volley中为了提高请求处理的速度,采用了ByteArrayPool进行内存中的数据存储的,如果下载大量的数据,这个存储空间就会溢出,所以不适合大量的数据,但是由于他的这个存储空间是内存中分配的,当存储的时候优是从ByteArrayPool中取出一块已经分配的内存区域, 不必每次存数据都要进行内存分配,而是先查找缓冲池中有无适合的内存区域,如果有,直接拿来用,从而减少内存分配的次数 ,所以他比较适合据量小,通信量大网络数据交互情况。
用法:
- 创建一个RequestQueue对象。
- 创建一个StringRequest对象。
- 将StringRequest对象添加到RequestQueue里面。
主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。
1 |
|