美文网首页Android TVAndroid开源库今日看点
OkHttp3的源码探究(一)okHttp的使用场景

OkHttp3的源码探究(一)okHttp的使用场景

作者: wenju_song | 来源:发表于2017-03-14 08:38 被阅读1913次

    版权声明:本文为博主原创文章,转载请注明出处。

    一、前言
    对于开发者来说优秀的源码是最好的学习资源。通过阅读优质源码就相当于和大牛有一次对话。
    OkHttp是支持HTTP和HTTP/2的网络请求框架。自从Android4.4开始,Google已经开始将源码中的HttpURLConnection替换为OkHttp,而在Android6.0之后的SDK中google更是移除了对于HttpClient的支持,在项目中用的比较多的Retrofit同样是对OkHttp进行再次封装而来的。Okhttp3中提供了Builder,很好的使用了创建者设计模式。这里来探究一下Okhttp3的源码。

    注:这个系列的文章是根据OkHttp最新的版本3.6.0进行的。
    二,源码阅读心得
    对于开源项目源码的阅读,自己总结了以下的方式:
    1.先要了解该项目的基本用法。
    2.根据基本用法去查看各个模块的源码。
    3.在各个模块了解的基础上,再整体的去把握一下。
    三,前期准备
    工欲善其事必先利其器,很多源码分析的文章不会涉及到这一点,为了更好的方便读者进行源码阅读,这里向大家介绍一下前期的准备工作。

    阅读工具:IntelliJ IDEA ,和Android Studio快捷键类似。下载地址:idea
    源码下载:okhttp源码
    可以下载zip包然后导入或通过cvs的github选项在线下载。Okhttp是使用Maven构建,也可以了解一下Maven相关的知识。

    四,OkHttp3的源码目录。
    构建完成我们可以看到以下的目录:

    OKHttp源码目录

    五,OkHttp的使用场景。
    阅读源码的第一步先要了解它的使用场景。OkHttp的源码的使用场景在他的源码中有体现,就是上图中的samples。读者可以去代码中阅读,也可以来看我下面的例子。
    1.Get请求
    Get同步请求(提示:Android 要在子线程)

    public class GetExample {
      OkHttpClient client = new OkHttpClient();
    
      String run(String url) throws IOException {
        Request request = new Request.Builder()
            .url(url)
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          return response.body().string();
        }
      }
    
      public static void main(String[] args) throws IOException {
        GetExample example = new GetExample();
        String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
        System.out.println(response);
      }
    }
    

    Get异步请求:

    public final class AsynchronousGet {
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
        //异步请求添加了callback
        client.newCall(request).enqueue(new Callback() {
          @Override public void onFailure(Call call, IOException e) {
            e.printStackTrace();
          }
    
          @Override public void onResponse(Call call, Response response) throws IOException {
            try (ResponseBody responseBody = response.body()) {
              if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
              Headers responseHeaders = response.headers();
              for (int i = 0, size = responseHeaders.size(); i < size; i++) {
                System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
              }
    
              System.out.println(responseBody.string());
            }
          }
        });
      }
    
      public static void main(String... args) throws Exception {
        new AsynchronousGet().run();
      }
    }
    

    2.Post请求
    Post同步请求:

    public class PostExample {
      public static final MediaType JSON
          = MediaType.parse("application/json; charset=utf-8");
    
      OkHttpClient client = new OkHttpClient();
    
      String post(String url, String json) throws IOException {
        RequestBody body = RequestBody.create(JSON, json);
        Request request = new Request.Builder()
            .url(url)
            .post(body)
            .build();
        try (Response response = client.newCall(request).execute()) {
          return response.body().string();
        }
      }
    
      String bowlingJson(String player1, String player2) {
        return "{'winCondition':'HIGH_SCORE',"
            + "'name':'Bowling',"
            + "'round':4,"
            + "'lastSaved':1367702411696,"
            + "'dateStarted':1367702378785,"
            + "'players':["
            + "{'name':'" + player1 + "','history':[10,8,6,7,8],'color':-13388315,'total':39},"
            + "{'name':'" + player2 + "','history':[6,10,5,10,10],'color':-48060,'total':41}"
            + "]}";
      }
    
      public static void main(String[] args) throws IOException {
        PostExample example = new PostExample();
        String json = example.bowlingJson("Jesse", "Jake");
        String response = example.post("http://www.roundsapp.com/post", json);
        System.out.println(response);
      }
    }
    
    

    Post异步和Get类似,添加CallBack
    3.添加请求头信息和获得请求头信息
    OkHttp的API,试图使这两种情况下都能舒适使用。当写请求头,用header(name, value)来为唯一出现的name设置value。如果它本身存在值,在添加新的value之前,他们会被移除。使用addHeader(name, value)来添加头部不需要移除当前存在的headers。

    public final class AccessHeaders {
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("https://api.github.com/repos/square/okhttp/issues")
            .header("User-Agent", "OkHttp Headers.java")
            .addHeader("Accept", "application/json; q=0.5")
            .addHeader("Accept", "application/vnd.github.v3+json")
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          System.out.println("Server: " + response.header("Server"));
          System.out.println("Date: " + response.header("Date"));
          System.out.println("Vary: " + response.headers("Vary"));
        }
      }
    
      public static void main(String... args) throws Exception {
        new AccessHeaders().run();
      }
    }
    

    这里看到Vary字段,想了解的同学可以看看这个HTTP 协议中 Vary 的一些研究
    4.登录认证
    我们在访问一个网站的时候,有时候要去登录认证后才能访问,如:
    http://publicobject.com/secrets/hellosecret.txt ,OkHttp提供了这样的功能。

    public final class Authenticate {
      private final OkHttpClient client;
    
      public Authenticate() {
        client = new OkHttpClient.Builder()
            .authenticator(new Authenticator() {
              @Override public Request authenticate(Route route, Response response) throws IOException {
                if (response.request().header("Authorization") != null) {
                  return null; // Give up, we've already attempted to authenticate.
                }
    
                System.out.println("Authenticating for response: " + response);
                System.out.println("Challenges: " + response.challenges());
                String credential = Credentials.basic("jesse", "password1");
                return response.request().newBuilder()
                    .header("Authorization", credential)
                    .build();
              }
            })
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/secrets/hellosecret.txt")
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          System.out.println(response.body().string());
        }
      }
    
      public static void main(String... args) throws Exception {
        new Authenticate().run();
      }
    }
    

    5.设置请求缓存

    public final class CacheResponse {
      private final OkHttpClient client;
    
      public CacheResponse(File cacheDirectory) throws Exception {
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        Cache cache = new Cache(cacheDirectory, cacheSize);
    
        client = new OkHttpClient.Builder()
            .cache(cache)
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
    
        String response1Body;
        try (Response response1 = client.newCall(request).execute()) {
          if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
    
          response1Body = response1.body().string();
          System.out.println("Response 1 response:          " + response1);
          System.out.println("Response 1 cache response:    " + response1.cacheResponse());
          System.out.println("Response 1 network response:  " + response1.networkResponse());
        }
    
        String response2Body;
        try (Response response2 = client.newCall(request).execute()) {
          if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
    
          response2Body = response2.body().string();
          System.out.println("Response 2 response:          " + response2);
          System.out.println("Response 2 cache response:    " + response2.cacheResponse());
          System.out.println("Response 2 network response:  " + response2.networkResponse());
        }
    
        System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
      }
    
      public static void main(String... args) throws Exception {
        new CacheResponse(new File("CacheResponse.tmp")).run();
      }
    }
    

    执行的结果如下:

    Response 1 response:          Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
    Response 1 cache response:    null
    Response 1 network response:  Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
    
    Response 2 response:          Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
    Response 2 cache response:    Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
    Response 2 network response:  null
    Response 2 equals Response 1? true
    

    从结果来看第二次的cache response不为null,而network response为null。
    6.取消请求

    public class CancelCall {
      private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();
    
        final long startNanos = System.nanoTime();
        final Call call = client.newCall(request);
    
        // Schedule a job to cancel the call in 1 second.
        executor.schedule(new Runnable() {
          @Override public void run() {
            System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
            call.cancel();
            System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
          }
        }, 1, TimeUnit.SECONDS);
    
        System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
        try (Response response = call.execute()) {
          System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
              (System.nanoTime() - startNanos) / 1e9f, response);
        } catch (IOException e) {
          System.out.printf("%.2f Call failed as expected: %s%n",
              (System.nanoTime() - startNanos) / 1e9f, e);
        }
      }
    
      public static void main(String... args) throws Exception {
        new CancelCall().run();
      }
    }
    

    ScheduledExecutorService的介绍:Java并发包:ScheduledExecutorService
    System.nanoTime返回的是毫微秒为单位的,1秒=1000毫秒=1000 000微秒=1000 000 000 毫微秒。这里1e9f指的是1e9是幂指数,f表示布尔类型。
    例子的运行结果是:

    0.01 Executing call.
    1.01 Canceling call.
    1.01 Canceled call.
    1.02 Call failed as expected: java.net.SocketException: Socket closed
    

    这个例子是请求一个延迟两秒才有数据返回的接口,从结果来看在1s内将请求取消了。
    7.设置固定证书

    public final class CertificatePinning {
      private final OkHttpClient client;
    
      public CertificatePinning() {
        client = new OkHttpClient.Builder()
            .certificatePinner(
                new CertificatePinner.Builder()
                    .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
                    .build())
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("https://publicobject.com/robots.txt")
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          for (Certificate certificate : response.handshake().peerCertificates()) {
            System.out.println(CertificatePinner.pin(certificate));
          }
        }
      }
    
      public static void main(String... args) throws Exception {
        new CertificatePinning().run();
      }
    }
    

    固定证书避免了信任证书颁发机构的需要。
    8.配置超时时间

    public final class ConfigureTimeouts {
      private final OkHttpClient client;
    
      public ConfigureTimeouts() throws Exception {
        client = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          System.out.println("Response completed: " + response);
        }
      }
    
      public static void main(String... args) throws Exception {
        new ConfigureTimeouts().run();
      }
    }
    

    这个例子当配置为下面的情况时可以看到异常:

      public ConfigureTimeouts() throws Exception {
        client = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .readTimeout(1, TimeUnit.SECONDS)
            .build();
      }
    

    9.获得网络请求的相关信息

    public final class LoggingInterceptors {
      private static final Logger logger = Logger.getLogger(LoggingInterceptors.class.getName());
      private final OkHttpClient client = new OkHttpClient.Builder()
          .addInterceptor(new LoggingInterceptor())
          .build();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("https://publicobject.com/helloworld.txt")
            .build();
    
        Response response = client.newCall(request).execute();
        response.body().close();
      }
    
      private static class LoggingInterceptor implements Interceptor {
        @Override public Response intercept(Chain chain) throws IOException {
          long t1 = System.nanoTime();
          Request request = chain.request();
          logger.info(String.format("Sending request %s on %s%n%s",
              request.url(), chain.connection(), request.headers()));
          Response response = chain.proceed(request);
    
          long t2 = System.nanoTime();
          logger.info(String.format("Received response for %s in %.1fms%n%s",
              request.url(), (t2 - t1) / 1e6d, response.headers()));
          return response;
        }
      }
    
      public static void main(String... args) throws Exception {
        new LoggingInterceptors().run();
      }
    }
    

    运行的结果:

    Mar 13, 2017 3:41:32 PM okhttp3.recipes.LoggingInterceptors$LoggingInterceptor intercept
    INFO: Sending request https://publicobject.com/helloworld.txt on null
    
    Mar 13, 2017 3:41:35 PM okhttp3.recipes.LoggingInterceptors$LoggingInterceptor intercept
    INFO: Received response for https://publicobject.com/helloworld.txt in 2682.8ms
    Server: nginx/1.10.0 (Ubuntu)
    Date: Mon, 13 Mar 2017 07:41:26 GMT
    Content-Type: text/plain
    Content-Length: 1759
    Last-Modified: Tue, 27 May 2014 02:35:47 GMT
    Connection: keep-alive
    ETag: "5383fa03-6df"
    Accept-Ranges: bytes
    

    这里我们可以看到请求的信息和返回的数据信息。
    10.上传文件

    public final class PostFile {
      public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        File file = new File("README.md");
    
        Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          System.out.println(response.body().string());
        }
      }
    
      public static void main(String... args) throws Exception {
        new PostFile().run();
      }
    }
    

    11.上传表单
    访问wiki并搜索"Jurassic Park"
    在url中相当于访问:[https://en.wikipedia.org/w/index.php?search=Jurassic Park](https://en.wikipedia.org/w/index.php?search=Jurassic Park)

    public final class PostForm {
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        RequestBody formBody = new FormBody.Builder()
            .add("search", "Jurassic Park")
            .build();
        Request request = new Request.Builder()
            .url("https://en.wikipedia.org/w/index.php")
            .post(formBody)
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          System.out.println(response.body().string());
        }
      }
    
      public static void main(String... args) throws Exception {
        new PostForm().run();
      }
    }
    

    12.上传表单,添加多个属性

    public final class PostMultipart {
      /**
       * The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
       * these examples, please request your own client ID! https://api.imgur.com/oauth2
       */
      private static final String IMGUR_CLIENT_ID = "9199fdef135c122";
      private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
        RequestBody requestBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("title", "Square Logo")
            .addFormDataPart("image", "logo-square.png",
                RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
            .build();
    
        Request request = new Request.Builder()
            .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
            .url("https://api.imgur.com/3/image")
            .post(requestBody)
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          System.out.println(response.body().string());
        }
      }
    
      public static void main(String... args) throws Exception {
        new PostMultipart().run();
      }
    }
    

    13.上传流

    public final class PostStreaming {
      public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        RequestBody requestBody = new RequestBody() {
          @Override public MediaType contentType() {
            return MEDIA_TYPE_MARKDOWN;
          }
    
          @Override public void writeTo(BufferedSink sink) throws IOException {
            sink.writeUtf8("Numbers\n");
            sink.writeUtf8("-------\n");
            for (int i = 2; i <= 997; i++) {
              sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
            }
          }
    
          private String factor(int n) {
            for (int i = 2; i < n; i++) {
              int x = n / i;
              if (x * i == n) return factor(x) + " × " + i;
            }
            return Integer.toString(n);
          }
        };
    
        Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(requestBody)
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          System.out.println(response.body().string());
        }
      }
    
      public static void main(String... args) throws Exception {
        new PostStreaming().run();
      }
    }
    

    这里用到了okio,一个不错的框架,这里有一篇博客介绍的不错Android 善用Okio简化处理I/O操作
    14.上传字符串

    public final class PostString {
      public static final MediaType MEDIA_TYPE_MARKDOWN
          = MediaType.parse("text/x-markdown; charset=utf-8");
    
      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        String postBody = ""
            + "Releases\n"
            + "--------\n"
            + "\n"
            + " * _1.0_ May 6, 2013\n"
            + " * _1.1_ June 15, 2013\n"
            + " * _1.2_ August 11, 2013\n";
    
        Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          System.out.println(response.body().string());
        }
      }
    
      public static void main(String... args) throws Exception {
        new PostString().run();
      }
    }
    

    15.得到进度

    public final class Progress {
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("https://publicobject.com/helloworld.txt")
            .build();
    
        final ProgressListener progressListener = new ProgressListener() {
          @Override public void update(long bytesRead, long contentLength, boolean done) {
            System.out.println(bytesRead);
            System.out.println(contentLength);
            System.out.println(done);
            System.out.format("%d%% done\n", (100 * bytesRead) / contentLength);
          }
        };
    
        OkHttpClient client = new OkHttpClient.Builder()
            .addNetworkInterceptor(new Interceptor() {
              @Override public Response intercept(Chain chain) throws IOException {
                Response originalResponse = chain.proceed(chain.request());
                return originalResponse.newBuilder()
                    .body(new ProgressResponseBody(originalResponse.body(), progressListener))
                    .build();
              }
            })
            .build();
    
        try (Response response = client.newCall(request).execute()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
          System.out.println(response.body().string());
        }
      }
    
      public static void main(String... args) throws Exception {
        new Progress().run();
      }
    
      private static class ProgressResponseBody extends ResponseBody {
    
        private final ResponseBody responseBody;
        private final ProgressListener progressListener;
        private BufferedSource bufferedSource;
    
        public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
          this.responseBody = responseBody;
          this.progressListener = progressListener;
        }
    
        @Override public MediaType contentType() {
          return responseBody.contentType();
        }
    
        @Override public long contentLength() {
          return responseBody.contentLength();
        }
    
        @Override public BufferedSource source() {
          if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
          }
          return bufferedSource;
        }
    
        private Source source(Source source) {
          return new ForwardingSource(source) {
            long totalBytesRead = 0L;
    
            @Override public long read(Buffer sink, long byteCount) throws IOException {
              long bytesRead = super.read(sink, byteCount);
              // read() returns the number of bytes read, or -1 if this source is exhausted.
              totalBytesRead += bytesRead != -1 ? bytesRead : 0;
              progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
              return bytesRead;
            }
          };
        }
      }
    
      interface ProgressListener {
        void update(long bytesRead, long contentLength, boolean done);
      }
    }
    

    下载的时候可以拿到progress。
    16.响应webSocket

    public final class WebSocketEcho extends WebSocketListener {
      private void run() {
        OkHttpClient client = new OkHttpClient.Builder()
            .readTimeout(0,  TimeUnit.MILLISECONDS)
            .build();
    
        Request request = new Request.Builder()
            .url("ws://echo.websocket.org")
            .build();
        client.newWebSocket(request, this);
    
        // Trigger shutdown of the dispatcher's executor so this process can exit cleanly.
        client.dispatcher().executorService().shutdown();
      }
    
      @Override public void onOpen(WebSocket webSocket, Response response) {
        webSocket.send("Hello...");
        webSocket.send("...World!");
        webSocket.send(ByteString.decodeHex("deadbeef"));
        webSocket.close(1000, "Goodbye, World!");
      }
    
      @Override public void onMessage(WebSocket webSocket, String text) {
        System.out.println("MESSAGE: " + text);
      }
    
      @Override public void onMessage(WebSocket webSocket, ByteString bytes) {
        System.out.println("MESSAGE: " + bytes.hex());
      }
    
      @Override public void onClosing(WebSocket webSocket, int code, String reason) {
        webSocket.close(1000, null);
        System.out.println("CLOSE: " + code + " " + reason);
      }
    
      @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        t.printStackTrace();
      }
    
      public static void main(String... args) {
        new WebSocketEcho().run();
      }
    }
    

    WebSocket是基于H5的一种全双工通信,这里有一篇文章介绍得到很好。Java后端WebSocket的Tomcat实现
    六.总结
    到此OkHttp常用的场景已经介绍完了,从例子中我们可以学到很多的东西,下一篇开始源码阅读分析。


    推荐:
    欢迎关注我创建的Android TV 简书专题,会定期给大家分享一些AndroidTv相关的内容:
    http://www.jianshu.com/c/37efc6e9799b

    相关文章

      网友评论

      • mundane:你好, 请问你是怎么导入的, 我使用intellij idea导入okhttp的源码发生了错误, mavn也没有下载jar包下来
        字字珠玑:怎么搞定的啊?求指教
        mundane:@白兰鸽_699d 已经搞定了
        ed8e20871d09:不是应该是导依赖吗?

      本文标题:OkHttp3的源码探究(一)okHttp的使用场景

      本文链接:https://www.haomeiwen.com/subject/udkpittx.html