OkHttp源码解析(上)

本文详细解析了OkHttp的使用方式,包括get和post请求的同步异步处理,重点剖析了分发器Dispatcher和拦截器Interceptors的工作原理,以及责任链模式在重试、重定向和默认拦截器中的应用。

导语

学过Android开发的同学都知道,我们使用网络请求获取服务器的数据时,通常使用的是封装好的Retrofit框架,这个框架很好的帮助我们对网络的发起,以及返回的数据进行操作,我们使用起来十分方便,对于Retrofit来说,我们仅仅看到了它的表面,如何正确使用等,其内部还是要借助OkHtttp来请求网络的,Retrofit只是对OkHttp进行了再次的封装,而且Retrofit不具备网络请求功能,只是在OkHtttp的外表又套了一层,对返回的数据支持RxJava转换和Gson解析。真正起到网络请求的还是OkHttp,所以要了解里面的实质,我们还是着手从OkHttp出发,来探索它对网络的认知和对数据的传递。

OkHttp使用方式

要想了解其原理,首先得学会使用它,OkHttp的使用也非常简单。

从请求方式来讲,分为 getpost

get请求的同步和异步

// 构建一个OkHttpClient对象
OkHttpClient client = new OkHttpClient.Builder()
                .build();

// 创建一个Request请求对象
Request request = new Request.Builder()
        .get()
        .url("https://www.baidu.com/")
        .build();

// 把request对象 通过 newCall 转换成call
Call call = client.newCall(request);

try {
   
   
    // 通过call来发起网络请求
    // 同步请求
    Response response = call.execute();
    //返回响应体
    ResponseBody body = response.body();
    System.out.println(body.string());
} catch (IOException e) {
   
   
    e.printStackTrace();
}

// 通过call来发起网络请求
// 异步请求
call.enqueue(new Callback() {
   
   
    @Override
    public void onFailure(Call call, IOException e) {
   
   

    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
   
   
        // 返回响应体
        ResponseBody body = response.body();
        System.out.println(body.string());
    }
});

上面的步骤也非常清楚:

  1. 构建一个OkHttpClient对象,通过Builder来构建我们的client,我们自由配置client。(添加拦截器,超时时间等)
  2. 创建一个Request请求对象,Request对象通过构建者模式创建,我们可以自由地配置Request对象。
  3. 把request对象 通过 newCall 转换成call。
  4. 通过call来发起网络请求,同步请求使用execute,异步请求使用enqueue。
  5. 通过response.body()来获取响应体。

post请求的同步和异步

OkHttpClient client = new OkHttpClient.Builder()
        .build();

// 表单格式构建 RequestBody
RequestBody requestBody = new FormBody.Builder()
        .add("username", "admin")
        .add("password", "123456")
        .build();

Request request = new Request.Builder()
        .post(requestBody)
        .url("https://www.baidu.com/")
        .build();

Call call = client.newCall(request);
try {
   
   
    Response response = call.execute();
    ResponseBody responseBody = response.body();
    System.out.println(responseBody.string());
} catch (IOException e) {
   
   
    e.printStackTrace();
}

call.enqueue(new Callback() {
   
   
    @Override
    public void onFailure(Call call, IOException e) {
   
   

    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
   
   
        ResponseBody responseBody = response.body();
        System.out.println(responseBody.string());
    }
});

post请求与get请求的唯一区别就是:post请求通过RequestBody来构建一个请求体,Body里面带上我们要请求的参数;而get请求的参数是拼接在url后面。

了解了OkHttp的使用,我们梳理下整个OkHttp的调用过程。
在这里插入图片描述
不管我们通过execute还是enqueue,都会经过Dispatcher(分发器)和 Interceptors(拦截器)来获得Response。

分发器到底做了什么事情,接下来我们深入地了解分发器内部的原理。

分发器—Dispatcher

我们从上面的代码中知道,真正请求网络的是一个call对象,call是一个接口,通过RealCall实现,我们通过newCall(request)构建的这个call,通过execute或enqueue就能获得response,为何,进入execute看下:

@Override public Response execute() throws IOException {
   
   
  synchronized (this) {
   
   
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  try {
   
   
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
   
   
    eventListener.callFailed(this, e);
    throw e;
  } finally {
   
   
    client.dispatcher().finished(this);
  }
}

可以看到,调用了分发器的executed,然后通过getResponseWithInterceptorChain()获得响应。

synchronized void executed(RealCall call) {
   
   
  runningSyncCalls.add(call);
}

将call加入running队列。

对于同步请求,分发器并没有做什么事情。我们分析下异步请求的分发器干了什么事情。

@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));
}

调用了分发器的enqueue,注意这里:分发器将Callback包装成AsyncCall来传给了enqueue。AsyncCall继承自NamedRunnable,而NamedRunnable又实现了Runnable,所以AsyncCall本质上就是一个Runnable。AsyncCall交给了分发器:

synchronized void enqueue(AsyncCall call) {
   
   
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
   
   
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
   
   
    readyAsyncCalls.add(call);
  }
}
private int maxRequests = 64;
private int maxRequestsPerHost = 5;

这里通过两个条件判断:

  1. 正在执行的running队列里面的任务数小于最大请求数(64)以及同一Host的请求数小于最大同一Host请求数(5)时,将call加入running队列,然后交给线程池处理。否则,将AsyncCall加入ready队列。

    分别解释下这两个条件:

    1. 第一个条件:充分考虑到客户端的压力,如果没有这个64的限制,客户端不停的进行网络请求,这样会让客户端的压力特别大。
    2. 第二个条件:充分考虑到服务器的压力,如果同一个Host的服务器,没有这个限制,对于一个客户端就建立64次连接,如果有多个客户端同时建立连接的话会撑爆服务器。
  2. 如果把任务放在ready队列后,这个队列里的任务怎么执行,什么时候执行?

前面说到,正在执行的任务会交给线程池处理,当线程池处理完之后,会finish掉这个任务。由于AsyncCall本质上就是一个Runnable,所以会调用run方法,而run方法里面又调用了execute方法,execute方法是一个抽象方法,所以在分发器里实现如下:

public abstract class NamedRunnable implements Runnable {
   
   
  protected final String name;

  public NamedRunnable(String format, Object... args) {
   
   
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
   
   
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
   
   
      execute();
    } finally {
   
   
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}
@Override protected void execute() {
   
   
  boolean signalledCallback = false;
  try {
   
   
     // 执行请求(拦截器)
    Response response = getResponseWithInterceptorChain
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值