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(); } } 复制代码
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
Network Interceptors
在无网络的情况下打开App,App会加载本地缓存的数据;那么问题来了,既然缓存了本地数据,我们如何去缓存数据呢?
很简单,网络数据请求成功后立即保存一份在本地。问题来了,保存数据的逻辑写在那里?
方法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))); } } 复制代码
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源码及设计思想