Android-网络请求库okhttp源码阅读随笔

一:先看看okhttp简单的配置以及使用:

1. 在app的module中先配置依赖  implementation'com.squareup.okhttp3:okhttp:3.10.0'

2.okhttp执行网络请求调用方式:

    异步请求调用Call.enqueue();同步请求调用Call.execute()

Activity

二:框架基本流程源码剖析

由于OkhttpClient内部有非常复杂且多的参数配置,作为一个框架来说,为了让用户使用起来比较友好,采用了建造者模式,来构建OkhttpClient所需要的一些重要的参数配置项,这里就不用多说了。

然后我们先分析下一步代码Call call = okHttpClient.newCall(request);这里通过newCall得到了一个Call对象,根据代码调用链可以看出,newCall实际创建的是一个名叫RealCall的对象。        

/**

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

*/

@Override public CallnewCall(Request request) {

return RealCall.newRealCall(this, request, false /* for web socket */);

}

static RealCallnewRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {

// Safely publish the Call instance to the EventListener.

  RealCall call =new RealCall(client, originalRequest, forWebSocket);

  call.eventListener = client.eventListenerFactory().create(call);

  return call;

}

然后我们继续跟进,先看下enqueue这个异步请求究竟是怎么处理的

@Override public void enqueue(Callback responseCallback) {

synchronized (this) {

if (executed)throw new IllegalStateException("Already Executed");

    executed =true;

  }

captureCallStackTrace();

  eventListener.callStart(this);

  client.dispatcher().enqueue(new AsyncCall(responseCallback));

}

这里我们可以看到,他在这里做了个判断,如果同一个Call对象调用了多次enqueue,这里回抛出异常。接下来看关键代码,最终okhttp框架将Callback(也就是我们请求的时候传进来的回调函数)包装为了一个AysncCall(这个AysncCall实际是一个Runnable,最终由Dispatcher类中维护的线程池中的线程执行),交给了一个叫做Dispatcher的类,我们继续跟进...

synchronized void enqueue(AsyncCall call) {

if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost){

runningAsyncCalls.add(call);

    executorService().execute(call);

  }else {

readyAsyncCalls.add(call);

  }

}

Dispatcher

这里可以看到Dispatcher类里面维护了三个队列,包含运行中的异步请求队列runningAysncCalls,运行中的同步请求队列runningSyncCalls,以及待运行的异步请求队列readyAsyncCalls。在调用enqueue的时候,框架层先判断正在运行中的异步队列runningAysncCalls的个数是否小于最大请求数maxRequests(默认64),并且判断这个即将添加的请求的host在runningAysncCalls中是否小于maxRequestsPerHost(默认5),都满足则将这个封装由请求信息的Call对象添加到runningAysncCalls,并且交给线程池executorService执行,不满足则添加到准备队列readyAsyncCalls。刚才已经说过了AsyncCall是一个Runnable,也就是最终由线程执行到AsyncCall的execute方法,继续...


RealCall$$AsyncCall

这里可以看到execute方法最终是通过getResponseWithInterceptorChain()得到Response,然后通过我们在enqueue时传进来的那个Callback将结果回调出去。追踪同步请求代码调用链也发现,最终也是这个方法返回得到Response,那么接下来看看getResponseWithInterceptorChain

RealCall

从getResponseWithInterceptorChain这个方法,框架是将一些Interceptor添加到一个list中,然后创建了一个RealInterceptorChain,调用了它的proceed方法

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,

    RealConnection connection)throws IOException {

//************省略代码

// 这里又创建了一个RealInterceptorChain,不过这里有个关键参数不一样,那就是index +1,这里逻辑是先处理拦截器集合的Interceptor的intercept方法,处理每个拦截器自己的逻辑,然后通过index+1,取下一个拦截器执行interceptor.intercept

  RealInterceptorChain next =new RealInterceptorChain(interceptors, streamAllocation, httpCodec,

      connection, index +1, request, call, eventListener, connectTimeout, readTimeout,

      writeTimeout);

  Interceptor interceptor =interceptors.get(index);

  Response response = interceptor.intercept(next);

//************省略代码

return response;

}

其实这里是一个责任链模式的一个应用,okhttp框架将网络请求的一些步骤封装成了好几层(也就是拦截器interceptor),根据之前上面getResponseWithInterceptorChain的截图可以看到,有以下,简单描述下各自负责的内容:

RetryAndFollowUpInterceptor:网络请求重试以及重定向请求

BridgeInterceptor:主要处理请求里面Header的相关,包括gzip的压缩解压缩,cookie等

CacheInterceptor:这里做了缓存相关策略,比如没网络时但有缓存数据,可以直接返回,还比如说后台返回的数据给了时效性,下次请求的时候看到缓存数据有效,这个时候直接返回缓存数据,节省了网络开销

ConnectInterceptor:这里面有连接池connectionPool,okhttp是基于socket的一个封装,这里有socket连接的缓存

CallServerInterceptor:这里做了最终的网络请求操作与服务端交互

当然,这里可以根据自己的需求自定义拦截器,实现自己的逻辑。然后最终的响应Response通过回调返回后(代码在AsyncCall#execute()),在finally中调用了client.dispatcher().finished(this);这个方法所做的事是从runningAysncCalls中移除这个已经完成的请求,如果条件满足,将readyAsyncCalls中的请求添加到runningAysncCalls队列中并执行

private void finished(Deque calls, T call, boolean promoteCalls) {

int runningCallsCount;

  Runnable idleCallback;

  synchronized (this) {

if (!calls.remove(call))throw new AssertionError("Call wasn't in-flight!");

    if (promoteCalls) promoteCalls();

    runningCallsCount = runningCallsCount();

    idleCallback =this.idleCallback;

  }

if (runningCallsCount ==0 && idleCallback !=null) {

idleCallback.run();

  }

}

private void promoteCalls() {

if (runningAsyncCalls.size() >=maxRequests)return; // Already running max capacity.

  if (readyAsyncCalls.isEmpty())return; // No ready calls to promote.

  for (Iterator i =readyAsyncCalls.iterator(); i.hasNext(); ) {

AsyncCall call = i.next();

    if (runningCallsForHost(call)

i.remove();

      runningAsyncCalls.add(call);

      executorService().execute(call);

    }

if (runningAsyncCalls.size() >=maxRequests)return; // Reached max capacity.

  }

}

至此,关于okhttp一个完整的请求,基本梳理完成。内部的一些拦截器相关内容,后续有时间再深挖。

这里有一些点需要注意:

1.关于okhttp里面Dispatcher的线程池创建,这里采用的是SynchronousQueue<Runnable>,采用这个同步队列的原因是希望更快的将runnable交给线程池里面的线程去处理,一般来说,SynchronousQueue的size<=1。

2.Dispatcher的线程池线程数量最大为Integer.MAX_VALUE,疑问:这里不设上限,会不会有性能问题?答:这里其实不会的,虽然这里没做控制但是runningAsyncCalls这个执行中队列有做上限处理,所以不用担心。

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