okhttp

1.okhttp源码分析(一)——基本流程(超详细)

2.okhttp源码分析(二)——RetryAndFollowUpInterceptor过滤器

3.okhttp源码分析(三)——CacheInterceptor过滤器

4.okhttp源码分析(四)——ConnectInterceptor过滤器

5.okhttp源码分析(五)——CallServerInterceptor过滤器

前言

最近算是入了源码的坑了,什么东西总想按住ctrl看看源码的模样,这段时间在研究okhttp的源码,发现okhttp的源码完全不是简简单单的几天就可以啃下来的,那就一步一步来吧。

这篇博客主要是从okhttp的总体流程分析源码的执行过程,对okhttp源码有大体上的理解,从全局上看出okhttp的设计思想。

分析

1.OkHttpClient

既然是流程分析,使用过okhttp的都了解,首先需要初始化一个OkHttpClient对象。OkHttp支持两种构造方式

1.默认方式

publicOkHttpClient(){this(newBuilder());}

可以看到这种方式,不需要配置任何参数,也就是说基本参数都是默认的,调用的是下面的构造函数。

OkHttpClient(Builder builder) {...}

2.builder模式,通过Builder配置参数,最后通过builder()方法返回一个OkHttpClient实例。

publicOkHttpClientbuild(){returnnewOkHttpClient(this);}

OkHttpClient基本上就这样分析完了,里面的细节基本上就是用于初始化参数和设置参数的方法。所以也必要将大量的代码放上来占内容。。。,这里另外提一点,从OkHttpClient中可以看出什么设计模式哪

1.builder模式

2.外观模式

2.Request

构建完OkHttpClient后就需要构建一个Request对象,查看Request的源码你会发现,你找不多public的构造函数,唯一的一个构造函数是这样的。

Request(Builder builder){this.url=builder.url;this.method=builder.method;this.headers=builder.headers.build();this.body=builder.body;this.tag=builder.tag!=null?builder.tag:this;}

这意味着什么,当然我们构建一个request需要用builder模式进行构建,那么就看一下builder的源码。

publicBuildernewBuilder(){returnnewBuilder(this);}//builder===================publicBuilder(){this.method="GET";this.headers=new Headers.Builder();}Builder(Request request){this.url=request.url;this.method=request.method;this.body=request.body;this.tag=request.tag;this.headers=request.headers.newBuilder();}publicRequestbuild(){if(url==null)thrownewIllegalStateException("url == null");returnnewRequest(this);}

其实忽略其他的源码,既然这篇博客只是为了从总体流程上分析OkHttp的源码,所以我们主要着重流程源码上的分析。从上面的源码我们会发现,request的构建也是基于builder模式。

3.异步请求

这里注意一下,这里分析区分一下同步请求和异步请求,但其实实质的执行流程除了异步外,基本都是一致的。

构建完Request后,我们就需要构建一个Call,一般都是这样的Call call = mOkHttpClient.newCall(request);那么我们就返回OkHttpClient的源码看看。

/**

  * Prepares the {@code request} to be executed at some point in the future.

  */@OverridepublicCallnewCall(Request request){//工厂模式returnRealCall.newRealCall(this,request,false/* for web socket */);}

可以看到,这里实质上调用的是RealCall中的newRealCall方法,但是这里需要注意一点,那就是方法前面的@Override注解,看到这个注解我们就要意识到,这个方法不是继承就是实现接口。

publicclassOkHttpClientimplementsCloneable,Call.Factory,WebSocket.Factory{...}

可以看到OkhttpClient实现了Call.Factory接口。

//Call.javainterfaceFactory{CallnewCall(Requestrequest);}

从接口源码我们也可以看出,这个接口其实并不复杂,仅仅是定义一个newCall用于创建Call的方法,这里其实用到了工厂模式的思想,将构建的细节交给具体实现,顶层只需要拿到Call对象即可。

回到主流程,我们继续看RealCall中的newRealCall方法。

finalclassRealCallimplementsCall{...staticRealCallnewRealCall(OkHttpClientclient,RequestoriginalRequest,booleanforWebSocket){// Safely publish the Call instance to the EventListener.RealCallcall=newRealCall(client,originalRequest,forWebSocket);call.eventListener=client.eventListenerFactory().create(call);returncall;}...}

可以看到RealCall实现了Call接口,newRealCall这是一个静态方法,new了一个RealCall对象,并创建了一个eventListener对象,从名字也可以看出,这个是用来监听事件流程,并且从构建方法我们也可以看出,使用了工厂模式

privateRealCall(OkHttpClient client,Request originalRequest,boolean forWebSocket){this.client=client;this.originalRequest=originalRequest;this.forWebSocket=forWebSocket;//默认创建一个retryAndFollowUpInterceptor过滤器this.retryAndFollowUpInterceptor=newRetryAndFollowUpInterceptor(client,forWebSocket);}

重点来了,可以看到,在RealCall的构造函数中,除了基本的赋值外,默认创建一个retryAndFollowUpInterceptor过滤器,过滤器可以说是OkHttp的巨大亮点,后续的文章我会详细分析一些过滤器吧(能力有限,尽量全看看)。

现在Call创建完了,一般就到最后一个步骤了,将请求加入调度,一般的代码是这样的。

//请求加入调度call.enqueue(newCallback(){@OverridepublicvoidonFailure(Requestrequest,IOExceptione){}@OverridepublicvoidonResponse(finalResponseresponse)throwsIOException{//String htmlStr =  response.body().string();}});

可以看到这里调用了call的enqueue方法,既然这里的call->RealCall,所以我们看一下RealCall的enqueue方法。

@Overridepublicvoidenqueue(CallbackresponseCallback){synchronized(this){if(executed)thrownewIllegalStateException("Already Executed");executed=true;}captureCallStackTrace();eventListener.callStart(this);client.dispatcher().enqueue(newAsyncCall(responseCallback));}

1.首先利用synchronized加入了对象锁,防止多线程同时调用,这里先判断一下executed是否为true判断当前call是否被执行了,如果为ture,则抛出异常,没有则设置为true。

2.captureCallStackTrace()

privatevoidcaptureCallStackTrace(){ObjectcallStackTrace=Platform.get().getStackTraceForCloseable("response.body().close()");retryAndFollowUpInterceptor.setCallStackTrace(callStackTrace);}

可以看到这里大体上可以理解为为retryAndFollowUpInterceptor加入了一个用于追踪堆栈信息的callStackTrace,后面有时间再详细分析一下这部分吧,不影响总体流程理解。

3.eventListener.callStart(this);可以看到前面构建的eventListener起到作用了,这里先回调callStart方法。

4.client.dispatcher().enqueue(new AsyncCall(responseCallback));这里我们就需要先回到OkHttpClient的源码中。

publicDispatcherdispatcher(){returndispatcher;}

可以看出返回了一个仅仅是返回了一个DisPatcher对象,那么就追到Dispatcher源码中。

synchronizedvoidenqueue(AsyncCallcall){if(runningAsyncCalls.size()<maxRequests&&runningCallsForHost(call)<maxRequestsPerHost){runningAsyncCalls.add(call);executorService().execute(call);}else{readyAsyncCalls.add(call);}}

这里先对Dispatcher的成员变量做个初步的认识。

publicfinalclassDispatcher{privateintmaxRequests=64;privateintmaxRequestsPerHost=5;private@NullableRunnableidleCallback;/** Executes calls. Created lazily. */private@NullableExecutorServiceexecutorService;/** Ready async calls in the order they'll be run. */privatefinalDeque<AsyncCall>readyAsyncCalls=newArrayDeque<>();/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */privatefinalDeque<AsyncCall>runningAsyncCalls=newArrayDeque<>();/** Running synchronous calls. Includes canceled calls that haven't finished yet. */privatefinalDeque<RealCall>runningSyncCalls=newArrayDeque<>();...}

可以看到,这里用三个队列ArrayDeque用于保存Call对象,分为三种状态异步等待,同步running,异步running

所以这里的逻辑就比较清楚了。

synchronizedvoidenqueue(AsyncCallcall){if(runningAsyncCalls.size()<maxRequests&&runningCallsForHost(call)<maxRequestsPerHost){runningAsyncCalls.add(call);executorService().execute(call);}else{readyAsyncCalls.add(call);}}

当正在执行的异步队列个数小于maxRequest(64)并且请求同一个主机的个数小于maxRequestsPerHost(5)时,则将这个请求加入异步执行队列runningAsyncCall,并用线程池执行这个call,否则加入异步等待队列。这里可以看一下runningCallsForHost方法。

/** Returns the number of running calls that share a host with {@code call}. */privateintrunningCallsForHost(AsyncCallcall){intresult=0;for(AsyncCallc:runningAsyncCalls){if(c.host().equals(call.host()))result++;}returnresult;}

其实也是很好理解的,遍历了runningAsyncCalls,记录同一个Host的个数。

现在来看一个AsyncCall的源码,这块基本上是核心执行的地方了。

finalclassAsyncCallextendsNamedRunnable{。。。}

看一个类,首先看一下这个类的结构,可以看到AsyncCall继承了NameRunnable类。

publicabstractclassNamedRunnableimplementsRunnable{protectedfinalStringname;publicNamedRunnable(Stringformat,Object...args){this.name=Util.format(format,args);}@Overridepublicfinalvoidrun(){StringoldName=Thread.currentThread().getName();Thread.currentThread().setName(name);try{execute();}finally{Thread.currentThread().setName(oldName);}}protectedabstractvoidexecute();}

可以看到NamedRunnable是一个抽象类,首先了Runnable接口,这就很好理解了,接着看run方法,可以看到,这里将当前执行的线程的名字设为我们在构造方法中传入的名字,接着执行execute方法,finally再设置回来。所以现在我们理所当然的回到AsyCall找execute方法了。

@Overrideprotectedvoidexecute(){boolean signalledCallback=false;try{//异步和同步走的是同样的方式,主不过在子线程中执行Response response=getResponseWithInterceptorChain();if(retryAndFollowUpInterceptor.isCanceled()){signalledCallback=true;responseCallback.onFailure(RealCall.this,newIOException("Canceled"));}else{signalledCallback=true;responseCallback.onResponse(RealCall.this,response);}}catch(IOException e){if(signalledCallback){// Do not signal the callback twice!Platform.get().log(INFO,"Callback failure for "+toLoggableString(),e);}else{eventListener.callFailed(RealCall.this,e);responseCallback.onFailure(RealCall.this,e);}}finally{client.dispatcher().finished(this);}}

终于,找到了Response的身影,那么就意味着执行网络请求就在getResponseWithInterceptorChain()方法中,后面的代码其实基本上就是一些接口回调,回调当前Call的执行状态,这里就不分析了,这里我们重点看一下getResponseWithInterceptorChain()这个方法,给我的感觉这个方法就是okHttp的精髓。

ResponsegetResponseWithInterceptorChain()throws IOException{// Build a full stack of interceptors.List<Interceptor>interceptors=newArrayList<>();interceptors.addAll(client.interceptors());//失败和重定向过滤器interceptors.add(retryAndFollowUpInterceptor);//封装request和response过滤器interceptors.add(newBridgeInterceptor(client.cookieJar()));//缓存相关的过滤器,负责读取缓存直接返回、更新缓存interceptors.add(newCacheInterceptor(client.internalCache()));//负责和服务器建立连接interceptors.add(newConnectInterceptor(client));if(!forWebSocket){//配置 OkHttpClient 时设置的 networkInterceptorsinterceptors.addAll(client.networkInterceptors());}//负责向服务器发送请求数据、从服务器读取响应数据(实际网络请求)interceptors.add(newCallServerInterceptor(forWebSocket));Interceptor.Chainchain=newRealInterceptorChain(interceptors,null,null,null,0,originalRequest,this,eventListener,client.connectTimeoutMillis(),client.readTimeoutMillis(),client.writeTimeoutMillis());returnchain.proceed(originalRequest);}

可以看到,这里首先new了一个Interceptor的ArrayList,然后分别加入了各种各样的Interceptor,所以当我们默认创建okHttpClient时,okHttp默认会给我们实现这些过滤器,每个过滤器执行不同的任务,这个思想太屌了有木有,每个过滤器负责自己的任务,各个过滤器间相互不耦合,高内聚,低耦合,对拓展放开巴拉巴拉等一系列设计思想有木有,这里可以对比一下Volley源码中的思想,Volley的处理是将缓存,网络请求等一系列操作揉在一起写,导致用户对于Volley的修改只能通过修改源码方式,而修改就必须要充分阅读理解volley整个的流程,可能一部分的修改会影响全局的流程,而这里,将不同的职责的过滤器分别单独出来,用户只需要对关注的某一个功能项进行理解,并可以进行扩充修改,一对比,okHttp在这方面的优势立马体现出来了。这里大概先描述一下几个过滤器的功能:

retryAndFollowUpInterceptor——失败和重定向过滤器

BridgeInterceptor——封装request和response过滤器

CacheInterceptor——缓存相关的过滤器,负责读取缓存直接返回、更新缓存

ConnectInterceptor——负责和服务器建立连接,连接池等

networkInterceptors——配置 OkHttpClient 时设置的 networkInterceptors

CallServerInterceptor——负责向服务器发送请求数据、从服务器读取响应数据(实际网络请求)

添加完过滤器后,就是执行过滤器了,这里也很重要,一开始看比较难以理解。

Interceptor.Chain chain=newRealInterceptorChain(interceptors,null,null,null,0,originalRequest,this,eventListener,client.connectTimeoutMillis(),client.readTimeoutMillis(),client.writeTimeoutMillis());returnchain.proceed(originalRequest);

可以看到这里创建了一个RealInterceptorChain,并调用了proceed方法,这里注意一下0这个参数。

publicResponseproceed(Requestrequest,StreamAllocationstreamAllocation,HttpCodechttpCodec,RealConnectionconnection)throws IOException{if(index>=interceptors.size())thrownewAssertionError();calls++;// If we already have a stream, confirm that the incoming request will use it.if(this.httpCodec!=null&&!this.connection.supportsUrl(request.url())){thrownewIllegalStateException("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){thrownewIllegalStateException("network interceptor "+interceptors.get(index-1)+" must call proceed() exactly once");}// Call the next interceptor in the chain.RealInterceptorChainnext=newRealInterceptorChain(interceptors,streamAllocation,httpCodec,connection,index+1,request,call,eventListener,connectTimeout,readTimeout,writeTimeout);Interceptorinterceptor=interceptors.get(index);Responseresponse=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){thrownewIllegalStateException("network interceptor "+interceptor+" must call proceed() exactly once");}// Confirm that the intercepted response isn't null.if(response==null){thrownewNullPointerException("interceptor "+interceptor+" returned null");}if(response.body()==null){thrownewIllegalStateException("interceptor "+interceptor+" returned a response with no body");}returnresponse;}

第一眼看,脑袋可能会有点发麻,稍微处理一下。

publicResponseproceed(Requestrequest,StreamAllocationstreamAllocation,HttpCodechttpCodec,RealConnectionconnection)throws IOException{if(index>=interceptors.size())thrownewAssertionError();calls++;。。。// Call the next interceptor in the chain.RealInterceptorChainnext=newRealInterceptorChain(interceptors,streamAllocation,httpCodec,connection,index+1,request,call,eventListener,connectTimeout,readTimeout,writeTimeout);Interceptorinterceptor=interceptors.get(index);Responseresponse=interceptor.intercept(next);。。。returnresponse;}

这样就很清晰了,这里index就是我们刚才的0,也就是从0开始,如果index超过了过滤器的个数抛出异常,后面会再new一个RealInterceptorChain,而且会将参数传递,并且index+1了,接着获取index的interceptor,并调用intercept方法,传入新new的next对象,这里可能就有点感觉了,这里用了递归的思想来完成遍历,为了验证我们的想法,随便找一个interceptor,看一下intercept方法。

publicfinalclassConnectInterceptorimplementsInterceptor{publicfinalOkHttpClientclient;publicConnectInterceptor(OkHttpClientclient){this.client=client;}@OverridepublicResponseintercept(Chainchain)throwsIOException{RealInterceptorChainrealChain=(RealInterceptorChain)chain;。。。暂时没必要看。。。returnrealChain.proceed(request,streamAllocation,httpCodec,connection);}}

可以看到这里我们拿了一个ConnectInterceptor的源码,这里得到chain后,进行相应的处理后,继续调用proceed方法,那么接着刚才的逻辑,index+1,获取下一个interceptor,重复操作,所以现在就很清楚了,这里利用递归循环,也就是okHttp最经典的责任链模式

4.同步请求

异步看完,同步其实就很好理解了。

/**

  * 同步请求

  */@OverridepublicResponseexecute()throws IOException{//检查这个call是否运行过synchronized(this){if(executed)thrownewIllegalStateException("Already Executed");executed=true;}captureCallStackTrace();//回调eventListener.callStart(this);try{//将请求加入到同步队列中client.dispatcher().executed(this);//创建过滤器责任链,得到responseResponse result=getResponseWithInterceptorChain();if(result==null)thrownewIOException("Canceled");returnresult;}catch(IOException e){eventListener.callFailed(this,e);throwe;}finally{client.dispatcher().finished(this);}}

可以看到基本上流程都一致,除了是同步执行,核心方法走的还是getResponseWithInterceptorChain()方法。

okHttp流程

到这里okHttp的流程基本上分析完了,接下来就是对Inteceptor的分析了,这里献上一张偷来的图(原图来源->拆轮子系列:拆 OkHttp)便于理解流程,希望能分析完所有的Inteceptor吧!

OkHttp源码

作者:被代码淹没的小伙子

链接:https://www.jianshu.com/p/37e26f4ea57b

来源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容