OKHttp网络优化

本章节讲述OkHttp框架可以操作的网络优化

一.EventListener类 OkHttp网络请求耗时统计

1.EventListener类继承类

/**
 * EventListener监听实现类
 */

public class OkHttpEventListener extends EventListener {

    /**
     * 请求开始
     */

    private long mCallStartTime;
    private long mDnsStartTime;
    private long mConnectStartTime;
    private long mSecureConnectStartTime;
    private long mConnectionStartTime;
    private long mRequestHeadersStartTime;
    private long mRequestBodyStartTime;
    private long mResponseHeadersStartTime;
    private long mResponseBodyStartTime;

    /**
     * 请求开始
     */

    @Override
    public void callStart(Call call) {
        super.callStart(call);
        mCallStartTime = System.nanoTime();
    }

    /**
     * 请求正常结束
     */

    @Override
    public void callEnd(Call call) {
        super.callEnd(call);
        logMethod(call.request().toString() + "方法的 callEnd", mCallStartTime);
    }

    /**
     * 请求异常结束
     */

    @Override
    public void callFailed(Call call, IOException ioe) {
        super.callFailed(call, ioe);
        logMethod(call.request().toString() + "方法的 callFailed", mCallStartTime);
    }

    /**
     * dns解析开始
     * DNS解析是请求DNS(Domain Name System)服务器。将域名解析成ip的过程。
     */

    @Override
    public void dnsStart(Call call, String domainName) {
        super.dnsStart(call, domainName);
        mDnsStartTime = System.nanoTime();
    }

    /**
     * dns解析结束
     */

    @Override
    public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
        super.dnsEnd(call, domainName, inetAddressList);
        logMethod(call.request().toString() + "方法的 dnsEnd", mDnsStartTime);
    }

    /**
     * 连接开始
     */

    @Override
    public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
        super.connectStart(call, inetSocketAddress, proxy);
        mConnectStartTime = System.nanoTime();
    }

    /**
     * 连接结束
     */

    @Override
    public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol) {
        super.connectEnd(call, inetSocketAddress, proxy, protocol);
        logMethod(call.request().toString() + "方法的 connectEnd", mConnectStartTime);
    }

    /**
     * 连接失败
     */

    @Override
    public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol, IOException ioe) {
        super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe);
        logMethod(call.request().toString() + "方法的 connectFailed", mConnectStartTime);
    }

    /**
     * TLS安全连接开始
     */

    @Override
    public void secureConnectStart(Call call) {
        super.secureConnectStart(call);
        mSecureConnectStartTime = System.nanoTime();
    }

    /**
     * TLS安全连接结束
     */

    @Override
    public void secureConnectEnd(Call call, Handshake handshake) {
        super.secureConnectEnd(call, handshake);
        logMethod(call.request().toString() + "方法的 secureConnectEnd", mSecureConnectStartTime);
    }

    /**
     * 连接绑定
     */

    @Override
    public void connectionAcquired(Call call, Connection connection) {
        super.connectionAcquired(call, connection);
        mConnectionStartTime = System.nanoTime();
    }

    /**
     * 连接释放
     */

    @Override
    public void connectionReleased(Call call, Connection connection) {
        super.connectionReleased(call, connection);
        logMethod(call.request().toString() + "方法的 connectionReleased", mConnectionStartTime);
    }

    /**
     * 请求Header开始
     */

    @Override
    public void requestHeadersStart(Call call) {
        super.requestHeadersStart(call);
        mRequestHeadersStartTime = System.nanoTime();
    }

    /**
     * 请求Header结束
     */

    @Override
    public void requestHeadersEnd(Call call, Request request) {
        super.requestHeadersEnd(call, request);
        logMethod(call.request().toString() + "方法的 requestHeadersEnd", mRequestHeadersStartTime);
    }

    /**
     * 请求Body开始
     */

    @Override
    public void requestBodyStart(Call call) {
        super.requestBodyStart(call);
        mRequestBodyStartTime = System.nanoTime();
    }

    /**
     * 请求Body结束
     */

    @Override
    public void requestBodyEnd(Call call, long byteCount) {
        super.requestBodyEnd(call, byteCount);
        logMethod(call.request().toString() + "方法的 requestBodyEnd", mRequestBodyStartTime);
    }

    /**
     * 响应Header开始
     */

    @Override
    public void responseHeadersStart(Call call) {
        super.responseHeadersStart(call);
        mResponseHeadersStartTime = System.nanoTime();
    }

    /**
     * 响应Header结束
     */

    @Override
    public void responseHeadersEnd(Call call, Response response) {
        super.responseHeadersEnd(call, response);
        logMethod(call.request().toString() + "方法的 responseHeadersEnd", mResponseHeadersStartTime);
    }

    /**
     * 响应Body开始
     */

    @Override
    public void responseBodyStart(Call call) {
        super.responseBodyStart(call);
        mResponseBodyStartTime = System.nanoTime();
    }

    /**
     * 响应Body结束
     */

    @Override
    public void responseBodyEnd(Call call, long byteCount) {
        super.responseBodyEnd(call, byteCount);
        logMethod(call.request().toString() + "方法的 responseBodyEnd", mResponseBodyStartTime);
    }

    /**
     * 打印公共方法
     */

    private void logMethod(String name, long startTime) {
        long time = System.nanoTime() - startTime;
        Log.d("OkHttpOptimizeActivity", name + "方法 耗时:" + (time / 1000000000d) + "秒");
    }

}

2.添加eventListener监听

public class OkHttpOptimizeActivity extends AppCompatActivity {

    private String getUrl = "http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue";
    private String postUrl = "http://fanyi.youdao.com/openapi.do";

    private OkHttpClient mOkHttpClient = null;//OkHttpClient 对象


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_okhttp_optimize);

        //创建OkHttpClient对象
        mOkHttpClient = new OkHttpClient.Builder()
                .connectTimeout(20, TimeUnit.SECONDS)//连接时间
                .readTimeout(20, TimeUnit.SECONDS)//读时间
                .writeTimeout(20, TimeUnit.SECONDS)//写时间
                .retryOnConnectionFailure(true)//连接失败后是否重新连接
                .dns(new OkHttpDNS())//DNS域名解析
                
                .eventListener(new OkHttpEventListener())//EventListener监听 可以统计各种时间
                
                .build();

        findViewById(R.id.activity_okhttp_optimize_textview1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Get请求测试
                getData();
                //Post请求测试
                Map<String, String> params = new HashMap<>();
                params.put("keyfrom", "imoocdict123456");
                params.put("key", "324273592");
                params.put("type", "data");
                params.put("doctype", "json");
                params.put("version", "1.1");
                params.put("q", "red");
                postData(params);
            }
        });
    }

    /**
     * OkHttp Get请求
     */

    private void getData() {
        //1.创建Request对象 构建一个具体的网络请求对象 包括具体的请求url&请求头&请求体等等 tag设置每个接口的tag 用于区分那个方法
        Request request = new Request.Builder().url(getUrl).get().tag("FA0010").build();
        //2.获取Call对象 将具体的网络请求与执行请求的实体进行绑定,形成一个具体的正式的可执行实体
        Call call = mOkHttpClient.newCall(request);
        //3.Call对象进行网络请求 enqueue异步  execute同步一般不用
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d("OkHttpOptimizeActivity", "Get请求 onFailure方法执行 错误信息----:" + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.d("OkHttpOptimizeActivity", "Get请求 onResponse方法执行报文----:" + response.body().string());
                }
            }
        });
    }

    /**
     * OkHttp Post请求
     */

    private void postData(final Map<String, String> params) {
        //1.获取FormBody.Builder对象
        FormBody.Builder builder = new FormBody.Builder();
        //2.遍历params 将其放入到FormBody.Builder 对象
        if (params != null && !params.isEmpty()) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                builder.add(entry.getKey(), entry.getValue());
            }
        }
        //3.获取RequestBody 对象
        RequestBody requestBody = builder.build();
        //4.创建Request对象 构建一个具体的网络请求对象 包括具体的请求url&请求头&请求体等等 tag设置每个接口的tag 用于区分那个方法
        Request request = new Request.Builder().url(postUrl).post(requestBody).tag("FA0011").build();
        //5.获取Call对象 将具体的网络请求与执行请求的实体进行绑定,形成一个具体的正式的可执行实体
        Call call = mOkHttpClient.newCall(request);
        //6.Call对象进行网络请求 enqueue异步  execute同步一般不用
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d("OkHttpOptimizeActivity", "Post请求 onFailure方法执行 错误信息----:" + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.d("OkHttpOptimizeActivity", "Post请求 onResponse方法执行报文----:" + response.body().string());
                }
            }
        });
    }

}

3.结果

Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 dnsEnd方法 耗时:0.004301719秒


Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 dnsEnd方法 耗时:0.004300885秒


Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 connectEnd方法 耗时:0.044430729秒


Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 connectEnd方法 耗时:0.04443151秒


Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 requestHeadersEnd方法 耗时:6.17344E-4秒


Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 requestHeadersEnd方法 耗时:6.71146E-4秒


Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 requestBodyEnd方法 耗时:7.63646E-4秒


Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 responseHeadersEnd方法 耗时:0.093491979秒


Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 responseBodyEnd方法 耗时:0.003987135秒


Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 connectionReleased方法 耗时:0.102222969秒


Request{method=GET, url=http://fanyi.youdao.com/openapi.do?keyfrom=imoocdict123456&key=324273592&type=data&doctype=json&version=1.1&q=blue, tags={class java.lang.Object=FA0010}}方法的 callEnd方法 耗时:0.159830573秒


Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 responseHeadersEnd方法 耗时:0.163656458秒


Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 responseBodyEnd方法 耗时:0.001606042秒


Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 connectionReleased方法 耗时:0.170009584秒


Request{method=POST, url=http://fanyi.youdao.com/openapi.do, tags={class java.lang.Object=FA0011}}方法的 callEnd方法 耗时:0.227597239秒

4.说明

<1> OkHttp框架提供了EventListener抽象类,可以方便的让开发者获取各种网络时间,例如DNS解析、TSL/SSL连接、Response接收等。

<2> EventListener抽象类中重写的方法,每个方法都有一个Call对象。可以使用这个对象获取网络请求的很多参数。

       比如 获取 Request request() 从而获取 等等

final HttpUrl url;
final String method;
final Headers headers;

     

二.DNS优化

1.简介

DNS(Domain Name System),它的作用就是   根据域名  查出  对应的IP地址。它是 HTTP 协议的前提。只有将域名正确的解析成 IP 地址后,后面的 HTTP 流程才可以继续进行下去。

在咱们的App访问网络的时候。DNS解析是网络请求的第一步。默认我们使用运营商LocalDNS服务。3G 网络下,耗时在 200~300ms左右。4G 网络下需要 100ms左右。

解析慢  并不 LocalDNS最大的问题,它还存在一些更为严重的问题。例如:DNS 劫持、DNS 调度不准确(缓存、转发、NAT)导致性能退化等等,这些才是网络优化最应该解决的问题。

想要优化 DNS,现在最简单成熟的方案,就是使用 HTTPDNS

DNS和HTTPDNS区别

DNS:不仅支持 UDP,它还支持 TCP,但是大部分标准的 DNS 都是基于 UDP 与 DNS 服务器的 53 端口进行交互。

HTTPDNS:顾名思义它是利用 HTTP协议DNS服务器的80端口进行交互。不走传统的 DNS 解析,从而绕过运营商的LocalDNS服务器,有效的防止了域名劫持提高域名解析的效率

2.代码实现

OKHttp框架实现DNS转换成HTTPDNS。有两种方式。

<1> 拦截器实现

<2> Dns接口实

OkHttp提供了Dns接口专门用来实现DNS优化

接口实现类

public class OkHttpDNS implements Dns {

    private String mIpString;

    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        Log.d("OkHttpDNS", "hostname----:" + hostname);
        if (TextUtils.isEmpty(hostname) || null == hostname) return SYSTEM.lookup(hostname);

        //根据域名获取IP
        InetAddress[] ips = InetAddress.getAllByName(hostname);
        for (InetAddress inetAddress : ips) {
            mIpString = inetAddress.getHostAddress();
            Log.d("OkHttpDNS", "mIpString----:" + mIpString);
        }

        if (null != mIpString && !TextUtils.isEmpty(mIpString)) {
            return Arrays.asList(InetAddress.getAllByName(mIpString));
        } else {
            return SYSTEM.lookup(hostname);
        }
    }
}

添加接口

mOkHttpClient = new OkHttpClient.Builder()
        .connectTimeout(20, TimeUnit.SECONDS)//连接时间
        .readTimeout(20, TimeUnit.SECONDS)//读时间
        .writeTimeout(20, TimeUnit.SECONDS)//写时间
        .retryOnConnectionFailure(true)//连接失败后是否重新连接
                
        .dns(new OkHttpDNS())//DNS域名解析

        .eventListener(new OkHttpEventListener())//EventListener监听 可以统计各种时间
        .build();

三.gzip 压缩

1.简介

我们知道,在HTTP传输时是支持 gzip 压缩的,客户端发起请求时在请求头里增加 Accept-Encoding: gzip服务端响应时返回的头信息里增加 Content-Encoding: gzip,这表示传输的数据是采用 gzip 压缩的。默认情况下,传输内容是不压缩的,采用 gzip 压缩后可以大幅减少传输内容大小,这样可以提高传输速度,减少流量的使用。

OkHttp框架是默认支持GZip压缩的

和后台商量好后,可以自定义GZip压缩。

2.客户端实现

<1> Interceptor拦截器接口实现类

public class OkHttpGZip implements Interceptor {

    @NonNull
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();

        if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
            return chain.proceed(originalRequest);
        }

        Request compressedRequest = originalRequest.newBuilder()
                .header("Content-Encoding", "gzip")
                .method(originalRequest.method(), gzip(originalRequest.body()))
                .build();
        return chain.proceed(compressedRequest);
    }

    /**
     * 自定义GZip压缩
     */

    private RequestBody gzip(final RequestBody body) {
        return new RequestBody() {
            @Override
            public MediaType contentType() {
                return body.contentType();
            }

            @Override
            public long contentLength() {
                return -1;
            }

            @Override
            public void writeTo(BufferedSink sink) throws IOException {
                BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
                body.writeTo(gzipSink);
                gzipSink.close();
            }
        };
    }

}

<2> 添加拦截器

//创建OkHttpClient对象
mOkHttpClient = new OkHttpClient.Builder()
        .connectTimeout(20, TimeUnit.SECONDS)//连接时间
        .readTimeout(20, TimeUnit.SECONDS)//读时间
        .writeTimeout(20, TimeUnit.SECONDS)//写时间
        .retryOnConnectionFailure(true)//连接失败后是否重新连接
        .dns(new OkHttpDNS())//DNS域名解析
        .eventListener(new OkHttpEventListener())//EventListener监听 可以统计各种时间
        .hostnameVerifier(new OkHttpHostnameVerifier())//验签

        .addInterceptor(new OkHttpGZip())//添加拦截器

        .build();

<3> 说明

自定义GZip一定要和后台商议好,否则如果后台忽然有一天修改了压缩问题。就会导致客户端接口请求异常。

<4> 原因

因为OkHttp默认已经实现了GZip。在一个叫BridgeInterceptor类里面。

OKHttp框架的BridgeInterceptor源码

public final class BridgeInterceptor implements Interceptor {
  private final CookieJar cookieJar;

  public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }

  /** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */
  private String cookieHeader(List<Cookie> cookies) {
    StringBuilder cookieHeader = new StringBuilder();
    for (int i = 0, size = cookies.size(); i < size; i++) {
      if (i > 0) {
        cookieHeader.append("; ");
      }
      Cookie cookie = cookies.get(i);
      cookieHeader.append(cookie.name()).append('=').append(cookie.value());
    }
    return cookieHeader.toString();
  }
}

添加

boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
  transparentGzip = true;
  requestBuilder.header("Accept-Encoding", "gzip");
}

使用

if (transparentGzip&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {

   ...

    String contentType = networkResponse.header("Content-Type");
   ...

}

所以,如果没有特殊需求或者没有和后台商量好的情况下,最好不要自定义使用OkHttp框架的GZip。

四.连接池复用

简介

提高网络性能优化,很重要的一点就是降低延迟和提升响应速度。

比如我们在浏览器中发起请求的时候,header部分会有keep-alive字段。keep-alive就是浏览器和服务端之间保持长连接,这个连接是可以复用的。在HTTP1.1中是默认开启的。

那么OkHttp的连接池是怎么复用的呢?我们按照OkHttp3.X的源码看一下。

OkHttp源码中有一个类ConnectionPool。顾名思义,看名字就知道这个类就是OkHttp的连接池。

ConnectionPool源码

public final class ConnectionPool {
    private static final Executor executor;
    private final int maxIdleConnections;
    private final long keepAliveDurationNs;
    private final Runnable cleanupRunnable;
    private final Deque<RealConnection> connections;
    final RouteDatabase routeDatabase;
    boolean cleanupRunning;

    public ConnectionPool() {
        this(5, 5L, TimeUnit.MINUTES);
    }

    public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
        this.cleanupRunnable = new Runnable() {
            public void run() {
                while(true) {
                    long waitNanos = ConnectionPool.this.cleanup(System.nanoTime());
                    if (waitNanos == -1L) {
                        return;
                    }

                    if (waitNanos > 0L) {
                        long waitMillis = waitNanos / 1000000L;
                        waitNanos -= waitMillis * 1000000L;
                        synchronized(ConnectionPool.this) {
                            try {
                                ConnectionPool.this.wait(waitMillis, (int)waitNanos);
                            } catch (InterruptedException var8) {
                            }
                        }
                    }
                }
            }
        };
        this.connections = new ArrayDeque();
        this.routeDatabase = new RouteDatabase();
        this.maxIdleConnections = maxIdleConnections;
        this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
        if (keepAliveDuration <= 0L) {
            throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
        }
    }

    public synchronized int idleConnectionCount() {
        int total = 0;
        Iterator var2 = this.connections.iterator();

        while(var2.hasNext()) {
            RealConnection connection = (RealConnection)var2.next();
            if (connection.allocations.isEmpty()) {
                ++total;
            }
        }

        return total;
    }

    public synchronized int connectionCount() {
        return this.connections.size();
    }

    @Nullable
    RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
        assert Thread.holdsLock(this);

        Iterator var4 = this.connections.iterator();

        RealConnection connection;
        do {
            if (!var4.hasNext()) {
                return null;
            }

            connection = (RealConnection)var4.next();
        } while(!connection.isEligible(address, route));

        streamAllocation.acquire(connection, true);
        return connection;
    }

    @Nullable
    Socket deduplicate(Address address, StreamAllocation streamAllocation) {
        assert Thread.holdsLock(this);

        Iterator var3 = this.connections.iterator();

        RealConnection connection;
        do {
            if (!var3.hasNext()) {
                return null;
            }

            connection = (RealConnection)var3.next();
        } while(!connection.isEligible(address, (Route)null) || !connection.isMultiplexed() || connection == streamAllocation.connection());

        return streamAllocation.releaseAndAcquire(connection);
    }

    void put(RealConnection connection) {
        assert Thread.holdsLock(this);

        if (!this.cleanupRunning) {
            this.cleanupRunning = true;
            executor.execute(this.cleanupRunnable);
        }

        this.connections.add(connection);
    }

    boolean connectionBecameIdle(RealConnection connection) {
        assert Thread.holdsLock(this);

        if (!connection.noNewStreams && this.maxIdleConnections != 0) {
            this.notifyAll();
            return false;
        } else {
            this.connections.remove(connection);
            return true;
        }
    }

    public void evictAll() {
        List<RealConnection> evictedConnections = new ArrayList();
        synchronized(this) {
            Iterator i = this.connections.iterator();

            while(true) {
                if (!i.hasNext()) {
                    break;
                }

                RealConnection connection = (RealConnection)i.next();
                if (connection.allocations.isEmpty()) {
                    connection.noNewStreams = true;
                    evictedConnections.add(connection);
                    i.remove();
                }
            }
        }

        Iterator var2 = evictedConnections.iterator();

        while(var2.hasNext()) {
            RealConnection connection = (RealConnection)var2.next();
            Util.closeQuietly(connection.socket());
        }

    }

    long cleanup(long now) {
        int inUseConnectionCount = 0;
        int idleConnectionCount = 0;
        RealConnection longestIdleConnection = null;
        long longestIdleDurationNs = -9223372036854775808L;
        synchronized(this) {
            Iterator i = this.connections.iterator();

            while(i.hasNext()) {
                RealConnection connection = (RealConnection)i.next();
                if (this.pruneAndGetAllocationCount(connection, now) > 0) {
                    ++inUseConnectionCount;
                } else {
                    ++idleConnectionCount;
                    long idleDurationNs = now - connection.idleAtNanos;
                    if (idleDurationNs > longestIdleDurationNs) {
                        longestIdleDurationNs = idleDurationNs;
                        longestIdleConnection = connection;
                    }
                }
            }

            if (longestIdleDurationNs < this.keepAliveDurationNs && idleConnectionCount <= this.maxIdleConnections) {
                if (idleConnectionCount > 0) {
                    return this.keepAliveDurationNs - longestIdleDurationNs;
                }

                if (inUseConnectionCount > 0) {
                    return this.keepAliveDurationNs;
                }

                this.cleanupRunning = false;
                return -1L;
            }

            this.connections.remove(longestIdleConnection);
        }

        Util.closeQuietly(longestIdleConnection.socket());
        return 0L;
    }

    private int pruneAndGetAllocationCount(RealConnection connection, long now) {
        List<Reference<StreamAllocation>> references = connection.allocations;
        int i = 0;

        while(i < references.size()) {
            Reference<StreamAllocation> reference = (Reference)references.get(i);
            if (reference.get() != null) {
                ++i;
            } else {
                StreamAllocationReference streamAllocRef = (StreamAllocationReference)reference;
                String message = "A connection to " + connection.route().address().url() + " was leaked. Did you forget to close a response body?";
                Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);
                references.remove(i);
                connection.noNewStreams = true;
                if (references.isEmpty()) {
                    connection.idleAtNanos = now - this.keepAliveDurationNs;
                    return 0;
                }
            }
        }

        return references.size();
    }

    static {
        executor = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true));
    }
}

源码讲解

<1> 全局变量

//线程池,核心线程数为0,最大线程数为最大整数,线程空闲存活时间60s。SynchronousQueue 直接提交策略
private static final Executor executor;


//空闲连接的最大连接数
private final int maxIdleConnections;


//保持连接的周期
private final long keepAliveDurationNs;


//Runable
private final Runnable cleanupRunnable;


//双端队列,存放具体的连接
private final Deque<RealConnection> connections;


//用于记录连接失败的route
final RouteDatabase routeDatabase;

<2> 创建了一个线程池

static {
    executor = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true));
}

使用这个线程池,来异步执行如下的put操作。

<3> put操作

void put(RealConnection connection) {
    assert Thread.holdsLock(this);

    if (!this.cleanupRunning) {
        this.cleanupRunning = true;
        executor.execute(this.cleanupRunnable);
    }

    this.connections.add(connection);
}

完成两项内容

(1) 线程池异步执行put操作。

(2) 将连接对象RealConnection存放到Deque<RealConnection>集合。

<4> get操作

@Nullable
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert Thread.holdsLock(this);

    Iterator var4 = this.connections.iterator();

    RealConnection connection;
    do {
        if (!var4.hasNext()) {
            return null;
        }

        connection = (RealConnection)var4.next();
    } while(!connection.isEligible(address, route));

    streamAllocation.acquire(connection, true);
    return connection;
}

get方法中对存放具体连接的双端队列connections进行遍历,如果连接有效,则利用acquire()计数。

<5> 计数操作

public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert Thread.holdsLock(this.connectionPool);

    if (this.connection != null) {
        throw new IllegalStateException();
    } else {
        this.connection = connection;
        this.reportedAcquired = reportedAcquired;
        connection.allocations.add(new StreamAllocation.StreamAllocationReference(this, this.callStackTrace));
    }
}

使用StreamAllocation完成计数。

<6> 清空计数操作

private void release(RealConnection connection) {
    int i = 0;

    for(int size = connection.allocations.size(); i < size; ++i) {
        Reference<StreamAllocation> reference = (Reference)connection.allocations.get(i);
        if (reference.get() == this) {
            connection.allocations.remove(i);
            return;
        }
    }

    throw new IllegalStateException();
}

使用StreamAllocation完成清空计数。StreamAllocation使用的软引用。

总结

OkHttp3连接池的复用主要是对双端队列Deque<RealConnection>进行操作,通过对StreamAllocation的引用计数实现自动回收。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OkHttp 是一款非常优秀的网络请求框架,可以用来实现 Android 应用中的网络请求。如果要封装一个网络工具类,可以考虑以下几个步骤: 1. 引入 OkHttp 库。可以通过在 build.gradle 文件中添加以下依赖来引入 OkHttp 库: ``` implementation 'com.squareup.okhttp3:okhttp:4.9.0' ``` 2. 创建一个单例类来管理 OkHttp。在这个类中,我们可以创建一个 OkHttpClient 对象,用于发送网络请求。同时,可以在这个类中定义一些公共的请求参数、头部信息等,以便在发送网络请求时使用。 ```java public class HttpUtils { private static HttpUtils mInstance; private OkHttpClient mOkHttpClient; private HttpUtils() { mOkHttpClient = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .build(); } public static HttpUtils getInstance() { if (mInstance == null) { synchronized (HttpUtils.class) { if (mInstance == null) { mInstance = new HttpUtils(); } } } return mInstance; } public OkHttpClient getOkHttpClient() { return mOkHttpClient; } } ``` 3. 封装网络请求。在发送网络请求时,我们可以使用 OkHttp 提供的 Request 和 Response 类来实现。可以考虑封装一个方法,传入请求参数和回调函数,来发送网络请求,并将结果返回给回调函数。 ```java public void sendRequest(Request request, Callback callback) { mOkHttpClient.newCall(request).enqueue(callback); } ``` 4. 在回调函数中处理请求结果。在发送网络请求时,我们可以通过传入回调函数的方式,来处理请求结果。在回调函数中,可以根据请求结果的状态码、响应体等信息,来处理请求结果。 ```java public abstract class HttpCallback implements Callback { @Override public void onFailure(Call call, IOException e) { // 网络请求失败 } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { // 网络请求成功,处理响应结果 String result = response.body().string(); onSuccess(result); } else { // 网络请求失败 onFailure(response.code(), response.message()); } } public abstract void onSuccess(String result); public abstract void onFailure(int code, String message); } ``` 5. 封装常用的网络请求方法。根据业务需求,可以封装一些常用的网络请求方法,例如 GET、POST 等请求方法,以便在发送网络请求时使用。 ```java public void doGet(String url, HttpCallback callback) { Request request = new Request.Builder().url(url).build(); sendRequest(request, callback); } public void doPost(String url, RequestBody body, HttpCallback callback) { Request request = new Request.Builder().url(url).post(body).build(); sendRequest(request, callback); } ``` 通过以上步骤,我们可以封装一个简单的 OkHttp 网络工具类,用于发送网络请求。当然,具体的实现还需要根据业务需求进行调整和优化,以上仅供参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值