android面试(12)-Okhttp

可以说,okhttp非常的火,火了好几年了,虽然现如今比较火的网络框架是retrofit,但是,retrofit内部其实也是用了okhttp;

1.使用方法:

(1)创建一个请求客户端okhttpClient对象

(2)创建一个请求Request对象,通过Build模式创建

(3)创建一个实际的http请求call对象,它可以调用execute(同步获取数据),也可以调用enqueue(异步获取数据);

public class OkhttpFace extends Activity {

    //第一步:创建一个okHttpClient
    private final OkHttpClient client=new OkHttpClient();
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


    }

    private void okhttpAsyRequest() throws Exception{
        //第二步:创建一个Request,通过Builder模式生成
        Request request=new Request.Builder().url("http://www.baidu.com").build();

        //第三步,通过client的call方法创建一个call对象,代表实际的http请求
        Call call = client.newCall(request);

        //同步获取数据
        Response response = call.execute();

        //获取请求头
        Headers headers=response.headers();

        Log.e("请求数据为",response.body().toString());
    }


    private void okhttpSynRequest(){
        //第二步:创建一个Request,通过Builder模式生成
        Request request=new Request.Builder().url("http://www.baidu.com").build();

        //第三步,通过client的call方法创建一个call对象,代表实际的http请求
        Call call = client.newCall(request);

        //异步获取数据
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //获取请求头
                Headers headers=response.headers();

                Log.e("请求数据为",response.body().toString());
            }
        });
    }
}

这里的同步获取数据指的是,当调用execute方法是,它会阻塞线程去获取数据,异步获取数据指的是,它会开启一个新的线程,在新的线程中去获取数据,其中的callback回调都是工作在新线程中的;

2.源码分析:

我们先来看同步获取数据:

首先第一步没啥好说的,使用okhttp,每一个请求都需要一个client客户端,所以,你可以把它搞成全局的final对象;

第二步是采用Build模式新建一个request请求,这里提一下,build模式其实在开源框架中使用的很频繁,它的主要作用就是讲一个复杂的对象的创建和显示分离开来;

第三步call.execute方法,我们点进去看看:(其实真正调用execute的不是call,而是Realcall)

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

在execute方法中,首先它会做一个同步的检查,判断这个请求是否已经被执行了,因为每一个请求只能被执行一次,接下来,它会调用client的dispatcher()方法去执行网络请求,这里的dispatcher其实在它的官方文档上写的是执行异步操作的策略,但是,现在在同步里出现了,我们先不去关注他,因为一会的异步会出现很多这个dispatcher;继续往下看,getResponseWithInterceptorChian()这个方法可以说把okhttp最精髓的地方体现出来了,也就是----拦截器,最后,它会返回请求到的数据,然后把这次的请求关闭;

其实,真正做网络请求的,就是getResponseWithInterceptorChian()这个方法,我们需要看看:

private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(
      retryAndFollowUpInterceptor.isForWebSocket()));

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

这么多的interceptor,没错,这就是拦截器,okhttp最大的亮点就是这个interceptor,网络请求要很多步骤,比如需要去请求网络,缓存,透明压缩,等等一系列的操作,okhttp这个网络框架就把这些操作分割成一个个的interceptor,然后把这些interceptor连成一个链也就是Interceptor.Chain,然后去完成一次完整的网络请求,这是典型的分层思想的提现,把一次复杂的工作分步骤去做,更加合理,也更加高效安全;

就比如上面的代码中,一次请求就添加了失败重定向拦截器,桥接拦截器,缓存拦截器,连接网络的拦截器,连接服务器的拦截器,这些拦截器组成一条完整的链,这里,我们选一个拦截器看看内部是什么,我们就选这个CallServerInterceptor

@Override public Response intercept(Chain chain) throws IOException {
  HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
  StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
  Request request = chain.request();

  long sentRequestMillis = System.currentTimeMillis();
  httpStream.writeRequestHeaders(request);

  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }

  httpStream.finishRequest();

  Response response = httpStream.readResponseHeaders()
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  if (!forWebSocket || response.code() != 101) {
    response = response.newBuilder()
        .body(httpStream.openResponseBody(response))
        .build();
  }

  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    streamAllocation.noNewStreams();
  }

  int code = response.code();
  if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
    throw new ProtocolException(
        "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
  }

  return response;
}
这个方法内部其实就是取得请求request的头部和body,然后取得response的头部和body,大致就是这么一个流程,但是在这里,我们注意一个类,

 HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();

HttpStream类,这个类点进去看啊,它其实是集成了okio,我们又知道okio内部其实就是socket,所以我们就可以断定okhttp底层其实就是socket;

接下来,我们再来看看okhttp的异步请求的源码:

其实异步与同步最大的不同就是call调用的方法不一样,我们进enqueue方法看看

@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

这里我们发现,有事dispatcher,好吧,终究还是跑不掉,进去看看吧:

在dispatcher里,其实我们能看见三个非常重要的集合类:

/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

第一个是正在准备的异步请求

第二个是正在运行的异步请求,

第三个是正在运行的同步请求;

了解了这些之后,我们再看看dispatch的enqueue方法

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

从这里我们就可以看出他是怎么实现异步的了,首先它会判断当前的请求是否符合某些特定的要求,如果满足,那么就直接把这个请求添加到正在运行的异步请求集合里,并直接执行网络请求,如果不满足,那么就会把它加入到正在准备的异步请求集合中;其实在这里的executorService()内部就是一个线程池

public synchronized ExecutorService executorService() {
  if (executorService == null) {
    executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
  }
  return executorService;
}
说到底,异步和同步其实就是在处理多个请求时,同步是将其阻塞,异步是封装一个线程池,最后真正去执行网络请求的其实还是interceptor。


  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值