OkHttp源码学习和应用

  OkHttp是由Square创建的一个开源项目,旨在成为一个高效的HTTP和HTTP/2客户端。它可以有效地执行HTTP请求,加快请求的负载和节省带宽。它提供了几个强大的功能,如同一主机的所有HTTP/2请求共享一个套接字;HTTP/2不可用时,连接池减少请求时延;Transparent GZIP减少下载大小;响应缓存可以完全避免重复网络请求。此外,OkHttp有一个很好的机制来管理常见的连接问题。现在,它也支持WebSocket。

  拦截器是OkHttp中提供一种强大机制,它可以实现网络监听、请求以及响应重写、请求失败重试等功能。恰当地使用拦截器非常重要,笔者目前正在开发维护的项目中的诸多功能都与OkHttp的拦截器相关,比如App本地缓存,移动流量免流,容灾,域名劫持等,同时,OkHttp拦截器的设计思路也非常值得开发者学习,比如开屏广告,播放器播放条件判断等。因此,我决定写一篇OkHttp拦截器的文章,通过源码分析介绍几个tips。

拦截器设计–一语道破

OkHttp的多个拦截器可以链接起来,形成一个链条。拦截器会按照在链条上的顺序依次执行。 拦截器在执行时,可以先对请求的 Request 对象进行修改;在得到响应的 Response 对象之后,可以进行修改之后再返回。见官方介绍 拦截器拦截顺序图
,问题来了,请问:为何这个顺序是这样的?

import java.io.IOException;
//拦截器接口
public interface Interceptor {

    MyResponse intercept(Chain chain) throws IOException;

    interface Chain {
        MyRequest request();

        MyResponse proceed(MyRequest request) throws IOException;
    }
}
复制代码
/**
 * Created by guokun on 2018/12/4.
 * Description: 模仿OkHttp Interceptor 责任链设计模式
 */

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public final class MyInterceptorChain implements Interceptor.Chain {
    private final List<Interceptor> interceptors;
    private final MyRequest myRequest;
    //    private int calls;
    private int index;

    public MyInterceptorChain(List<Interceptor> interceptors, int index, MyRequest myRequest) {
        this.interceptors = interceptors;
        this.index = index;
        this.myRequest = myRequest;
    }

    public static void main(String[] args) {

        OneInterceptor oneInterceptor = new OneInterceptor("one_Request", "one_response");
        TwoInterceptor twoInterceptor = new TwoInterceptor("two_request", "two_response");
        ThreeInterceptor threeInterceptor = new ThreeInterceptor("three_request", " three_response");
        final List<Interceptor> interceptors = new ArrayList<>();
        interceptors.add(oneInterceptor);
        interceptors.add(twoInterceptor);
        interceptors.add(threeInterceptor);

        final MyRequest mainRequest = new MyRequest("main ");
        MyInterceptorChain myInterceptorChain = new MyInterceptorChain(interceptors, 0, mainRequest);
        try {
            System.out.println(myInterceptorChain.proceed(mainRequest).getResponseDiscription());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    public MyRequest request() {
        return myRequest;
    }

    @Override
    public MyResponse proceed(MyRequest request) throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();
//        calls++;
        //递归调用每个拦截器
        MyInterceptorChain next = new MyInterceptorChain(interceptors, index + 1, request);
        Interceptor interceptor = interceptors.get(index);
        MyResponse response = interceptor.intercept(next);

        return response;
    }
}
复制代码
public class MyRequest {
    private String requestdiscription;

    public MyRequest() {
        System.out.println("request construct enter");
    }

    public MyRequest(String discription) {
        this.requestdiscription = discription;
    }

    public String getRequestdiscription() {
        return requestdiscription;
    }

    public void setRequestdiscription(String requestdis) {
        this.requestdiscription = this.requestdiscription + "   " + requestdis;
    }
}
复制代码
public class MyResponse {
    private String responseDiscription;

    public MyResponse() {
        System.out.println("response construct enter");
    }

    public MyResponse(String discription) {
        this.responseDiscription = discription;
    }

    public String getResponseDiscription() {
        return responseDiscription;
    }

    public void setResponseDiscription(String responseDis) {
        this.responseDiscription = this.responseDiscription + "  " + responseDis;
    }
}
复制代码
import java.io.IOException;

public final class OneInterceptor implements Interceptor {
    private String oneRequest;
    private String oneResponse;

    public OneInterceptor(String oneRequest, String oneResponse) {
        this.oneRequest = oneRequest;
        this.oneResponse = oneResponse;
    }

    @Override
    public MyResponse intercept(Chain chain) throws IOException {
        MyRequest myRequest = chain.request();
        myRequest.setRequestdiscription(oneRequest);
        System.out.println("one interceptor --------------request====" + myRequest.getRequestdiscription());

        MyResponse myResponse = chain.proceed(myRequest);
        myResponse.setResponseDiscription(oneResponse);
        System.out.println("one interceptor -----------------response=======" + myResponse.getResponseDiscription());
        return myResponse;
    }
}
复制代码
import java.io.IOException;

public final class TwoInterceptor implements Interceptor {
    private String twoRequest;
    private String twoResponse;

    public TwoInterceptor(String oneRequest, String oneResponse) {
        this.twoRequest = oneRequest;
        this.twoResponse = oneResponse;
    }

    @Override
    public MyResponse intercept(Chain chain) throws IOException {
        MyRequest myRequest = chain.request();
        myRequest.setRequestdiscription(twoRequest);
        System.out.println("two interceptor-----------request=======" + myRequest.getRequestdiscription());

        MyResponse myResponse = chain.proceed(myRequest);
//        MyResponse myResponse = testFor(null, chain);
        myResponse.setResponseDiscription(twoResponse);
        System.out.println("two interceptor---------response===" + myResponse.getResponseDiscription());
        return myResponse;
    }

    private MyResponse testFor(MyResponse myResponse, Chain chain) throws IOException {
        MyRequest myRequest = chain.request();
        MyResponse response = null;
        for (int i = 0; i < 2; i++) {
            MyRequest myRequest1 = new MyRequest(myRequest + "----" + i + "    " + myRequest.getRequestdiscription());
            response = chain.proceed(myRequest1);
        }

        return response;
    }
}
复制代码
import java.io.IOException;

public final class ThreeInterceptor implements Interceptor {
    private String threeRequest;
    private String threeResponse;

    public ThreeInterceptor(String oneRequest, String oneResponse) {
        this.threeRequest = oneRequest;
        this.threeResponse = oneResponse;
    }

    @Override
    public MyResponse intercept(Chain chain) throws IOException {
        MyRequest myRequest = chain.request();
        myRequest.setRequestdiscription(threeRequest);
        System.out.println("three interceptor ------------request=====" + myRequest.getRequestdiscription());

        MyResponse myResponse = new MyResponse("threeResponse ");
        System.out.println("three interceptor ------------response ======" + myResponse.getResponseDiscription());
        return myResponse;
    }
}
复制代码
one interceptor --------------request====main    one_Request
two interceptor-----------request=======main    one_Request   two_request
three interceptor ------------request=====main    one_Request   two_request   three_request
three interceptor ------------response ======threeResponse 
two interceptor---------response===threeResponse   two_response
one interceptor -----------------response=======threeResponse   two_response  one_response
threeResponse   two_response  one_response
复制代码

理解上述代码调用逻辑就不难理解OkHttp的拦截器的调用逻辑,源码RealInterceptorChain对应MyInterceptorChain

package okhttp3.internal.http;

import java.io.IOException;
import java.util.List;
import okhttp3.Connection;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.connection.StreamAllocation;

/**
 * A concrete interceptor chain that carries the entire interceptor chain: all application
 * interceptors, the OkHttp core, all network interceptors, and finally the network caller.
 */
public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final Connection connection;
  private final int index;
  private final Request request;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, Connection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }

  @Override public Connection connection() {
    return connection;
  }

  public StreamAllocation streamAllocation() {
    return streamAllocation;
  }

  public HttpCodec httpStream() {
    return httpCodec;
  }

  @Override public Request request() {
    return request;
  }
  //关键方法
  @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }
  //关键方法
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !sameConnection(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }
    //关键代码
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

  private boolean sameConnection(HttpUrl url) {
    return url.host().equals(connection.route().address().url().host())
        && url.port() == connection.route().address().url().port();
  }
}
复制代码

应用拦截器VS网络拦截器

OkHttp有两类拦截器–应用拦截器和网络拦截器,两者有很大的区别。

import static okhttp3.internal.platform.Platform.INFO;

final class RealCall implements Call {
  ......
  
  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //应用拦截器
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //网络拦截器
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }
}
复制代码

官网上有一段两者区别的解释和示例,笔者能力有限暂无法从代码给出相关的答案。

Application interceptors

  1. 无法操作中间的响应结果,比如当URL重定向发生以及请求重试等,只能操作客户端主动第一次请求以及最终的响应结果。
  2. 在任何情况下只会调用一次,即使这个响应来自于缓存。
  3. 可以监听观察这个请求的最原始未经改变的意图(请求头,请求体等),无法操作OkHttp为我们自动添加的额外的请求头,比如If-None-Match。
  4. 允许short-circuit (短路)并且允许不去调用Chain.proceed()。(编者注:这句话的意思是Chain.proceed()不需要一定要调用去服务器请求,但是必须还是需要返回Respond实例。那么实例从哪里来?答案是缓存。如果本地有缓存,可以从本地缓存中获取响应实例返回给客户端。这就是short-circuit (短路)的意思。。囧)
  5. 允许请求失败重试以及多次调用Chain.proceed()。

Network Interceptors

  1. 允许操作中间响应,比如当请求操作发生重定向或者重试等。
  2. 不允许调用缓存来short-circuit (短路)这个请求。(编者注:意思就是说不能从缓存池中获取缓存对象返回给客户端,必须通过请求服务的方式获取响应,也就是Chain.proceed())
  3. 可以监听数据的传输
  4. 允许Connection对象装载这个请求对象。(编者注:Connection是通过Chain.proceed()获取的非空对象)

APP本地缓存

在无网络的情况下打开App,App会加载本地缓存的数据;那么问题来了,既然缓存了本地数据,我们如何去缓存数据呢?

很简单,网络数据请求成功后立即保存一份在本地。问题来了,保存数据的逻辑写在那里?

  1. 加在每个请求方法里;
  2. 加在http请求里;

方法2显然是更好的方式,既简单又利于扩展和维护。OkHttp拦截器加一个缓存Interceptor即可。

import android.text.TextUtils;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;

import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import tingshu.bubei.netwrapper.CacheProcessor;

import static tingshu.bubei.netwrapper.CacheStrategy.BEFORE_NET_CACHE;
import static tingshu.bubei.netwrapper.CacheStrategy.IF_NET_FAIL_CACHE;
import static tingshu.bubei.netwrapper.CacheStrategy.IF_NET_SUCCEED_FORCE_CACHE;
import static tingshu.bubei.netwrapper.CacheStrategy.IF_NET_SUCCEED_RETURN_CACHE;
import static tingshu.bubei.netwrapper.CacheStrategy.SAVE_NET_CACHE;

public class CacheInterceptor implements Interceptor {

    public static final String FORCE_CACHE_WITHOUT_NO_NET_DATA = "force_cache_without_no_net_data";

    private int cacheStrategy;
    private CacheProcessor cacheProcessor;

    public CacheInterceptor(int strategy, CacheProcessor cacheProcessor) {
        this.cacheStrategy |= strategy;
        this.cacheProcessor = cacheProcessor;
        if(cacheProcessor==null){
            throw new RuntimeException("cacheProcessor not be null");
        }
    }


    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response;
        String cache = null;
        if((cacheStrategy&BEFORE_NET_CACHE) == BEFORE_NET_CACHE){//预先读取缓存
            cache = cacheProcessor.findCache(false);
        }

        if(!TextUtils.isEmpty(cache)){//读取缓存成功
            ResponseBody responseBody = ResponseBody.create(MediaType.parse("application/json; charset=utf-8"), cache);
            response = new Response.Builder()
                    .message(" ")
                    .request(request)
                    .code(200)
                    .protocol(Protocol.HTTP_1_1)
                    .body(responseBody)
                    .build();
        }else{//缓存没有,读取网络数据
            ResponseBody body = null;
            IOException ioException = null;
            try {
                response = chain.proceed(request);
                body = response.newBuilder().build().body();
            } catch (IOException exception) {
                response = null;
                ioException = exception;
            }
            if(body!=null&&response.code()==200){//访问网络成功
                MediaType mediaType = body.contentType();
                String json = null;
                try{
                    json = response.body().string();
                }catch(IllegalStateException e){
                    e.printStackTrace();
                }
                if(!TextUtils.isEmpty(json) && !"".equals(json) &&((cacheStrategy&SAVE_NET_CACHE)==SAVE_NET_CACHE)){//网络读取成功,并且需要存缓存
                    try {
                        JSONObject jsonObject = new JSONObject(json);
                        int status = jsonObject.optInt("status");
                        if(status == 0){
                            cacheProcessor.saveCache(json);
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                    //cacheProcessor.saveCache(json);
                    if((cacheStrategy&IF_NET_SUCCEED_RETURN_CACHE) == IF_NET_SUCCEED_RETURN_CACHE){//请求成功还是使用缓存中的数据
                        json = cacheProcessor.findCache(false);
                    } else if ((cacheStrategy&IF_NET_SUCCEED_FORCE_CACHE) == IF_NET_SUCCEED_FORCE_CACHE) {//请求网络成功强制读缓存
                        json = cacheProcessor.findCache(true);
                    }
                }else if(TextUtils.isEmpty(json)&&((cacheStrategy&IF_NET_FAIL_CACHE)==IF_NET_FAIL_CACHE)){//网络读取失败,并且需要强制使用缓存
                    json = cacheProcessor.findCache(true);
                }
                if(json==null){
                    json = "";
                }
                body = ResponseBody.create(mediaType, json);
                Headers headers = response.headers().newBuilder().build();
                response = response.newBuilder().headers(headers).body(body).build();
            }else if((cacheStrategy&IF_NET_FAIL_CACHE)==IF_NET_FAIL_CACHE){//网络访问不成功,并且需要强制读取缓存
                cache = cacheProcessor.findCache(true);
                if(!TextUtils.isEmpty(cache)){
                    ResponseBody responseBody = ResponseBody.create(MediaType.parse("application/json; charset=utf-8"), cache);
                    response = new Response.Builder()
                            .message(" ")
                            .request(request)
                            .code(200)
                            .protocol(Protocol.HTTP_1_1)
                            .body(responseBody)
                            .addHeader(FORCE_CACHE_WITHOUT_NO_NET_DATA, "true")
                            .build();
                }
            }

            if (response == null) {
                if (ioException == null) {
                    throw new IOException("访问服务器失败,页没有获取到缓存");
                } else {
                    throw ioException;
                }
            }
        }

        return response;
    }
}
复制代码

缓存处理器:获取数据和保存数据

//缓存处理器接口
public interface CacheProcessor {
    //获取缓存数据
    String findCache(boolean forceFind);
    //保存缓存数据
    void saveCache(String json);
}
复制代码

JsonCache缓存处理器

public class JsonCacheProcessor implements CacheProcessor {
    public static int DEFAULT_CACHE_TIME = 24;//默认缓存时长是24小时
    private String key;
    private float cacheHour;

    public JsonCacheProcessor(String key) {
        this.key = key;
        this.cacheHour = DEFAULT_CACHE_TIME;
    }

    public JsonCacheProcessor(String key, float cacheHour) {
        this.key = key;
        this.cacheHour = cacheHour;
    }

    @Override
    public String findCache(boolean forceFind) {
        MiniDataCache miniDataCache = LocalDataBaseHelper.getInstance().queryMiniCache(key);
        if (miniDataCache == null) return null;

        long version = Utils.getCurrentHourVersion(cacheHour);
        if (forceFind || miniDataCache.getVersion() == version) {
            return miniDataCache.getJsonData();
        }
        return null;
    }

    @Override
    public void saveCache(String json) {
        LocalDataBaseHelper.getInstance().insertOrReplaceMiniCache(new MiniDataCache(key, json, Utils.getCurrentHourVersion(cacheHour)));
    }
}
复制代码

免流服务–OkHttpClient配置

  • 代理请求:设置代理服务器,http请求添加头部信息。
return okHttpClient.newBuilder()
                                .proxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(/*"101.95.47.98"*/teleHttpHostName, TingshuTypeCast.intParseInt(teleHttpPort, TELEHTTPPORT_DEFAULT))))
//                                .addInterceptor(new RetryInterceptor())
                                .addInterceptor(new Interceptor() {
                                    @Override
                                    public Response intercept(Chain chain) throws IOException {
                                        final String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
                                        final String token = MD5(spid + spkey + chain.request().url().host() + timestamp + telephone);
                                      /*  Log.i("hgk", " tele http----------------teleHttpHostName ===" + teleHttpHostName +
                                                "------host =====" + chain.request().url().host() +
                                                "------token = " + token);*/
                                        Request newRequest = chain.request().newBuilder()
                                                .addHeader("spid", spid)
                                                .addHeader("x-up-calling-line-id", telephone)
                                                .addHeader("timestamp", timestamp)
                                                .addHeader("token", token)
                                                .build();

                                        Log.d(DEBUG_TEL_TAG, "telecom free traffic service has open (get method), http url ==" + newRequest.url().toString());
                                        Response response = chain.proceed(newRequest);
                                        if (response.code() != 200) {
                                            Log.d(DEBUG_TEL_TAG, "Error code! telecom free traffic service has open (get method), http url response code ==" + response.code());
                                        }
                                        return response;
                                    }
                                }).build();
复制代码
  • 代理鉴权
final String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
                        String url = Uri.parse(uri).getHost();
                        final String token = MD5(spid + spkey + url + timestamp + telephone);
                        final StringBuilder authenticatorStr = new StringBuilder("SPID=" + spid + "&");
                        authenticatorStr.append("URL=" + url + "&");
                        authenticatorStr.append("UID=" + telephone + "&");
                        authenticatorStr.append("TIMESTAMP=" + timestamp + "&"); //免流时长有限制
                        authenticatorStr.append("TOKEN=" + token);
                        String stringrs = authenticatorStr.toString();
//                        Log.i("hgk", " tele https-------------------authenticatorStr ==" + stringrs);
                        final String str = Base64.encodeToString(stringrs.getBytes(), Base64.NO_WRAP);  //参数很重要,不要换成其它。试出来的!
                        Log.d(DEBUG_TEL_TAG, "telecom free traffic service has open (get method), https url ==" + uri);
                        Authenticator authenticator = new Authenticator() {
                            @Nullable
                            @Override
                            public Request authenticate(Route route, Response response) throws IOException {
                                return response.request().newBuilder()
                                        .header("Proxy-Authorization", str)
                                        .build();
                            }
                        };
                        return okHttpClient.newBuilder()
                                .proxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(teleHttpsHostName, TingshuTypeCast.intParseInt(teleHttpsPort, TELEHTTPPORT_DEFAULT))))
                                .proxyAuthenticator(authenticator)
                                .addInterceptor(new Interceptor() {
                                    @Override
                                    public Response intercept(Chain chain) throws IOException {
                                        Response response = chain.proceed(chain.request());
                                        if (response.code() != 200) {
                                            Log.d(DEBUG_TEL_TAG, "Error code! telecom free traffic service has open (init method), https response code ==" + response.code()
                                                    + "  url ===" + response.request().url());
                                        }
                                        return response;
                                    }
                                })
                                .build();
复制代码

-替换请求: http://:/?xyz=:

参考文档

OkHttp3-拦截器(Interceptor)
Chain of Responsibility Design Pattern
OkHttp使用进阶 译自OkHttp Github官方教程
OkHttp3源码分析之拦截器Interceptor
深入理解OkHttp源码及设计思想

原文 

https://juejin.im/post/5e1b34d15188254d937d3540

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » OkHttp源码学习和应用

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址