网络库源码

网络框架比较

453842de-7718-465d-9dbd-210f6c940968

  • 在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 链接
unknown_filename.4

QQ截图20200405211138
QQ截图20200404114815

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
    //okhttp第一步
private final OkHttpClient client = new OkHttpClient();

public void okhttpAsyGet() throws Exception {
//okhttp第二步
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();

//okhttp第三步
okhttp3.Response response = client.newCall(request).execute();

if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}

System.out.println(response.body().string());
}

public void OkHttpSyncGet() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();

client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}


@Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());//只能获取一次,可以用string保存
}
});
}

String post(String url, String json) throws IOException {

RequestBody formBody = new FormEncodingBuilder()
.add("platform", "android")
.add("name", "bug")
.add("subject", "XXXXXXXXXXXXXXX")
.build();

Request request = new Request.Builder()
.url(url)
.post(body)
.build();

Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}

源码

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) 来发起请求和获取响应。
    1. RetryAndFollowlnterceptor:负责失败重试、重定向
    2. Bridgelnterceptor 负责向服务器发送请求数据,例如头消息、cookie 等等
    3. Cachelnterceptor: 读取缓存、更新缓存
    4. Connectlnterceptor:负责和服务器建立连接的。在这⾥,OkHttp 会创建出⽹络请求所需要的 TCP 连接(如果是 HTTP),或者是建⽴在 TCP 连接之上的 TLS 连接(如果是 HTTPS),并且会创建出对应的 HttpCodec 对象(⽤于编码解码 HTTP 请求)
    5. Networklnterceptor:从服务器读取响应数据,数据流拦截器,io 操作,报文封装和解析。
  • 每一个功能都只是一个 Interceptor,它们再连接成一个 Interceptor. Chain,最终完成一次网络请求。
  • 责任链模式,工厂模式,建造者模式

QQ截图20200404104435

unknown_filename.2|700

unknown_filename.1|700

其他类

  1. dispatcher :调度器,⽤于调度多线程发起⽹络请求。默认最大并发数 64,单域名最大并发 5。里面用到了线程池和队列,线程池核心数是0。可运行线程数通过队列的个数限制的(ArrayDeque<AsyncCall>),AsyncCall 是个 runable
1
2
3
4
//线程池,核心线程数为0,最大线程数为最大整数,线程空闲存活时间60s,//SynchronousQueue 直接提交策略private static final Executor executor = new ThreadPoolExecutor(0,
Integer.MAX_VALUE , 60L , TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

该线程池的核心线程数为 0,线程池最有能纳 Integer. MAX_VALUE 个线程,且线程的空闲存活时间为 60s(可以理解为 okhttp 随时可以创建新的线程来满足需要。可以保证网络的 I/O 任务有线程来处理,不被阻塞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
...
val host: String
get() = originalRequest.url.host
val request: Request //url/method/headers/body
get() = originalReques
...
fun executeOn(executorService: ExecutorService) {
...
try {
executorService.execute(this)
success = true

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
2
3
4
5
6
7
8
9
10
11
12
@Override
public void onFailure(Call call, IOException e) {
if (e instanceof SocketTimeoutException && isRetry && retryCount < MAX_RETRY_COUNT) {
retryCount++;
call = client.newCall(call.request());
call.enqueue(this);
} else {
NetWorkResultEntity resultEntity = new NetWorkResultEntity("", null);
resultEntity.setErrorMessage(e.getMessage());
stringAsyncEmitter.onNext(resultEntity);
}
}

addInterceptor 和 addNetworkInterceptor

addNetInterceptor 是添加网络拦截器,addInterceptor 是添加应用拦截器,如果看到 okhttp 的流程分析的知道:应用拦截器 addInterceptor 是在网络拦截器前执行的。

addInterceptor(应用拦截器)在前:

  1. 不需要担心中间过程的响应,如重定向和重试.
  2. 总是只调用一次,即使HTTP响应是从缓存中获取.
    原始数据,⽽不是没有加 Content-Length 的请求数据,或者 Body还没有被 gzip 解压的响应数据
  3. 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
  4. 允许短路而不调用 Chain.proceed(),即中止调用
  5. 允许重试,使 Chain.proceed()调用多次
    刚开始的时候

addNetworkInterceptor(网络拦截器):

  1. 能够操作中间过程的响应,如重定向和重试执行多次
  2. 当网络短路而返回缓存响应时不被调用.
  3. 只观察在网络上传输的数据.
  4. 携带请求来访问连接
    最后:做网络调试

开发者使⽤ addNetworkInterceptor(Interceptor) 所设置的,它们的⾏为逻辑和使⽤ addInterceptor(Interceptor) 创建的⼀样,但由于位置不同,所以这⾥创建的 Interceptor 会看到每个请求和响应的数据(包括重定向以及重试的⼀些中间请求和响应),并且看到的是完整原始数据,⽽不是没有加 Content-Length 的请求数据,或者 Body还没有被 gzip 解压的响应数据。多数情况,这个⽅法不需要被使⽤;

addInterceptor()和 addNetworklnterceptor()的区别是,通过 addInterceptor(添加的 interceptor是
了业务,而通过 addNetworkInterceptor0)添加的 Interceptor 是为了网络调试
具体在代码执行上,addNetworkInterceptor拦截到的是未经过处理的 HTTP 信息,例如如果网络响应对数据使用了 Gzip 压缩,你用 addNetworkInterceptors 添加的拦截器,拿到的 Response里是未解压的数据,而
addInterceptors)拿到的是解压后的数据。

自定义一个拦截器
addNetworkInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();

long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));

Response response = chain.proceed(request);

long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));

return response;
}
}

缓存

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class HttpCacheInterceptor implements Interceptor {

@Overridepublic Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetWorkHelper.isNetConnected(MainApplication.getContext())) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}

Response response = chain.proceed(request);

if (NetWorkHelper.isNetConnected(MainApplication.getContext())) {
int maxAge = 60 * 60; // 如果想要不缓存,直接时间设置为0,但是需要保存下来吧
response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
response.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
return response;
}}

//设置缓存100M
Cache cache = new Cache(new File(MainApplication.getContext().getCacheDir(),"httpCache"),1024 * 1024 * 100);
return new OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor(new HttpCacheInterceptor())
.build();

整体流程

unknown_filename.3|600

unknown_filename|600

Retrofit 原理

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public interface netApi {
@GET("repos/{owner}/{repo}/contributors")
Call<ResponseBody> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo);
}


public void retrofitHttpRequest() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();

netApi repo = retrofit.create(netApi.class);

retrofit2.Call<ResponseBody> call = repo.contributorsBySimpleGetCall("userName", "path");
call.enqueue(new retrofit2.Callback<ResponseBody>() {
@Override
public void onResponse(retrofit2.Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
//response
}

@Override
public void onFailure(retrofit2.Call<ResponseBody> call, Throwable t) {

}
});
}
}

源码

  • Retrofit 2.0底层依赖OkHttp实现,也就是说Retrofit本质上就是对OkHttp的更进一步封装,还支持Rxjava。Retrofit和其它Http库最大区别在于通过大范围使用注解简化Http请求(请求方式、请求参数)
  • 网络请求的工作本质上是OkHttp完成,而 Retrofit 仅负责网络请求接口的封装。
  • Retrofit 主要是在 create 方法中采用动态代理模式实现接口方法,这个过程构建了一个 ServiceMethod 对象。(扩展 apiservice 的功能)
1
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);

这里使用了 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
    19
    private 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
    13
    Builder(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>() {

    unknown_filename.5

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中取出一块已经分配的内存区域, 不必每次存数据都要进行内存分配,而是先查找缓冲池中有无适合的内存区域,如果有,直接拿来用,从而减少内存分配的次数 ,所以他比较适合据量小,通信量大网络数据交互情况。
用法:

  1. 创建一个RequestQueue对象。
  2. 创建一个StringRequest对象。
  3. 将StringRequest对象添加到RequestQueue里面。

主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//volley第一步
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
private void volleyStringRequest() {

//volley第二步
StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
//volley第三步
mQueue.add(stringRequest);
}

private void volleyJsonRequest() {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://www.sina.com/sports/101010100.html", null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.d("TAG", response.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(jsonObjectRequest);
}


网络库源码
http://peiniwan.github.io/2024/04/570c74729f44.html
作者
六月的雨
发布于
2024年4月6日
许可协议