OKHTTP学习之基础知识及运用

OKHTTP简介

okhttp是一个现代化的高效的http库。它能使资源下载的更快和节省带宽。 
okttp的高效体现在: 
* http/2 允许多个访问同一主机地址的请求共享同一个socket。 
* 连接池减少了请求的延时 
* 通过GZIP压缩下载时的文件大小 
* Response缓存机制避免了网络请求完成时其它重复的请求。

如果你配置了多个服务端的IP地址,网络请求失败时,okhttp能够切换ip地址进行重连。 
okhttp的使用非常简单和方便,通过一些简单明了的配置就可。 
okhttp访问网络的时候,支持同步阻塞调用和异步回调两种方式。 
okhttp支持Android 2.3以上版本,jdk版本为1.7以上。

以上是来自官网的翻译。我想说的是如果想要体会到okhttp的好处,就要了解Android网络访问的过去。正所谓是—-“如果你认识从前的我,也许会原谅现在的我。”。哦不对!!!应该是—–如果你认识从前的我,肯定会喜欢上现在的我。

网络访问的远古时代

HTTPUrlConnection

以前自学的时候,网络上介绍Android的访问方式是这样:

private void testUrlConnect(){
        String host = "http://blog.csdn.net/briblue";
        try {
            URL url = new URL(host);
            HttpURLConnection httpconn = (HttpURLConnection) url.openConnection();

            if(httpconn.getResponseCode()  == HttpURLConnection.HTTP_OK){

                InputStreamReader isr = new InputStreamReader(
                        httpconn.getInputStream(),"utf-8");
                int i = 0;
                String content = "";
                while((i = isr.read()) != -1){
                    content = content+(char)i;
                }

                isr.close();
                final String info = content;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mInfo.setText(info);
                    }
                });

            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

HttpClient

后来他来了一个兄弟,著名的Apache HttpClient,它的访问方式如下:

private void testHttpClient(){
    String host = "http://blog.csdn.net/briblue";
    DefaultHttpClient httpclient = new DefaultHttpClient();
    HttpGet httpget = new HttpGet(host);

    ResponseHandler<String> responseHandler = new BasicResponseHandler();

    try {
        String content = httpclient.execute(httpget,responseHandler);


        System.out.println(content);
    } catch (ClientProtocolException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }finally{
        httpclient.getConnectionManager().shutdown();
    }
    }

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

上面可以看到httpclient比httpurlclient使用起来还是方便很多。

Volley

再后来谷歌发布了一个重要的http库Volley。当时我还专门研究过,并应用到了自己的项目代码,这里不做简介。它的底层可以配置使用httpclient和httpurlconnection,当然也可以配置我们今天的主角okhttp.

volley的优势是小而频繁的网络请求,volley本身的字面意思就是万箭齐发。 
volley的缺点不适合上传和下载的操作。

android api 23删除了httpclient

Android6.0删除了httpclient的相关包,而推荐使用HttpUrlConnection.这个我不做评价,可能是HttpUrlConnection的性能更高吧。但HttpUrlConnection真心不好用,需要封装,除了Volley外,okhttp是个不错的选择,下面我们开始讲okhttp的知识

OKHttp基本使用

前面的文章有介绍了HTTPUrlConnection与HttpClient的基本使用,这里开始介绍OKHttp如何编写代码。

okhttp的包的导入

在AndroidStudio中在依赖中引入如下:

dependencies {
    ......
    compile 'com.squareup.okhttp3:okhttp:3.4.1'
    .....
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果是Eclipse,则去官网下载jar包。 
点击这里

GET

下面是一个简单的get请求。

private void testGet() {

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                OkHttpClient mOkClient = new OkHttpClient();
                final Request request = new Request.Builder().url("http://blog.csdn.net/briblue").build();
                final Response response = mOkClient.newCall(request).execute();
                final String info = response.body().string();

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mInfo.setText(info);
                    }
                });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

整个流程是 
1. 创建OkHttpClient对象。 
2. 创建Request对象 
3. 通过OkHttpClient对象调用请求,并得到Response. 
4. 得到Response的信息并进行相应的业务处理。

可以看到非常的简单

post

在这里多说一句,因为没有找到网上免费的可以进行post请求的api,所以自己用PHP写了一个简单的接受post请求的页面。 
用的环境是网络下下载的Phpnow套件,这是个绿色小巧的Php开发环境,下载解压到一个没有中文字符路径下,然后在package/htdocs目录下编写自己的代码就可以了。我编写了一个简单的testpost.php

<?php
$post = $GLOBALS['HTTP_RAW_POST_DATA'];
//打印传过来的post数据
echo "hello you send a message:".$post;

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

我原本是想解析json数据的,后来发现这玩意没有这扩展。所以只得打印传过来的json数据。大家想让服务端更牛x一点,可以下载wamp套件。它同样是一个Php开发运行环境,但功能丰富得多。

好了,android的如何用okhttp发送post请求?

private void testPost() {

        new Thread(new Runnable() {
            @Override
            public void run() {
                String host = "http://172.26.133.50//testpost.php";
                final MediaType JSON
                        = MediaType.parse("application/json; charset=utf-8");

                OkHttpClient client = new OkHttpClient();

                String json = "{\"username\":\"frank\",sex:\"man\"}";
                RequestBody body = RequestBody.create(JSON, json);
                Request request = new Request.Builder()
                        .url(host)
                        .post(body)
                        .build();

                Response response = null;
                try {
                    response = client.newCall(request).execute();
                    final String info = response.body().string();

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mInfo.setText(info);
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }


            }
        }).start();

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

运行的结果是界面上出现了如下字符串:

hello you send a message:{username:”frank”,sex:”man”}

同步与异步

前面示例讲得其实都是同步请求。因为它们用了execute()方法。 
方法原型如下:

Response execute()
          throws IOException

Invokes the request immediately, and blocks until the response can be processed or is in error. 
   
   
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

如果要用异步的方式的话,就要用另外一个方法enqueue 
方法原型如下:

void enqueue(Callback responseCallback)

Schedules the request to be executed at some point in the future. 
   
   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

异步发送请求

这里以get为例,改写上面的方法

private void  asyncGet(){
        final Request request = new Request.Builder().url("http://blog.csdn.net/briblue").build();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Call call = mOkClient.newCall(request);
                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {

                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        final String info = response.body().string();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                mInfo.setText(info);
                            }
                        });
                    }
                });
            }
        }).start();
    }

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

我们看到enqueue方法中有一个Callback对象,它有两个回调方法,对应于访问失败和接收到回复两种情况。 
我们在onResponse中拿到Response对象就可以得到服务器返回来的数据。

知道了get和post方法,我们基本上就可以用okhttp来进行简单的开发了。

Call

不知道大家注意了没

Response response = mOkClient.newCall(request).execute();
   
   
  • 1
  • 1

execute()方法是mOkClient.newCall(request)对象调用的。那么newCall()方法是什么?

 public Call newCall(Request request) {
    return new RealCall(this, request);
  }
   
   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

它返回的是一个Call对象。

Call是什么?

public interface Call

A call is a request that has been prepared for execution. A call can be canceled. As this object represents a single request/response pair (stream), it cannot be executed twice.

上面是官网的解释 
说的是一个Call代表一个准备执行的网络请求,它能够被取消掉。但是它不能被执行两次。 
我将前面的代码试验一下,让它重复请求,结果真的报错了报错信息如下:

Process: com.frank.okhttpsample, PID: 27965 
==Java.lang.IllegalStateException: Already Executed==

好的,我们再看它的源码:

public interface Call {
  /** Returns the original request that initiated this call. */
  Request request();


  Response execute() throws IOException;


  void enqueue(Callback responseCallback);

  /** Cancels the request, if possible. Requests that are already complete cannot be canceled. */
  void cancel();

  /**
   * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
   * #enqueue(Callback) enqueued}. It is an error to execute a call more than once.
   */
  boolean isExecuted();

  boolean isCanceled();

  interface Factory {
    Call newCall(Request request);
  }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

可以得知Call其实是一个接口,它的接口方法有request()、execute()、enqueue()、cancel(),isExecuted()、isCanceled()。 
其中execute()和enqueue()方法我们已经见识过。

而cancel()方法上面的注释写得很明白。如果请求已经完成了是不能够被取消的。

Callback

大家还记得么?enqueue()方法中接受一个Callback的参数

public interface Callback {
  /**
   * Called when the request could not be executed due to cancellation, a connectivity problem or
   * timeout. Because networks can fail during an exchange, it is possible that the remote server
   * accepted the request before the failure.
   */
  void onFailure(Call call, IOException e);

  /**
   * Called when the HTTP response was successfully returned by the remote server. The callback may
   * proceed to read the response body with {@link Response#body}. The response is still live until
   * its response body is {@linkplain ResponseBody closed}. The recipient of the callback may
   * consume the response body on another thread.
   *
   * <p>Note that transport-layer success (receiving a HTTP response code, headers and body) does
   * not necessarily indicate application-layer success: {@code response} may still indicate an
   * unhappy HTTP response code like 404 or 500.
   */
  void onResponse(Call call, Response response) throws IOException;
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

callback也是一个接口,onFailure()方法在请求失败或者超时被调用。

onResponse()返回服务器回复的内容。

Response

Callback中的回调onResponse()当中有Response参数,那么什么是Response呢?

public final class Response 
extends Object 
implements Closeable

An HTTP response. Instances of this class are not immutable: the response body is a one-shot value that may be consumed only once and then closed. All other properties are immutable.

This class implements Closeable. Closing it simply closes its response body. See ResponseBody for an explanation and examples.

上面说,Response是一个Http Response,它的实例并不是固定不变的,它的内容reponse body只能被消费一次,而其它所有的属性是稳定不变的。

我们再看看它简单的源码:

public final class Response implements Closeable {
  private final Request request;
  private final Protocol protocol;
  private final int code;
  private final String message;
  private final Handshake handshake;
  private final Headers headers;
  private final ResponseBody body;
    ......

  private volatile CacheControl cacheControl; // Lazily initialized.

  private Response(Builder builder) {
  .....
    this.request = builder.request;
    this.protocol = builder.protocol;
    this.code = builder.code;
    this.message = builder.message;
    .....
  }


  public Request request() {
    return request;
  }


  public Protocol protocol() {
    return protocol;
  }

  /** Returns the HTTP status code. */
  public int code() {
    return code;
  }

  /**
   * Returns true if the code is in [200..300), which means the request was successfully received,
   * understood, and accepted.
   */
  public boolean isSuccessful() {
    return code >= 200 && code < 300;
  }

  /** Returns the HTTP status message or null if it is unknown. */
  public String message() {
    return message;
  }


  public List<String> headers(String name) {
    return headers.values(name);
  }


  public Headers headers() {
    return headers;
  }



  /**
   * Never {@code null}, must be closed after consumption, can be consumed only once.
   */
  public ResponseBody body() {
    return body;
  }

  public Builder newBuilder() {
    return new Builder(this);
  }


    ......


  /** Closes the response body. Equivalent to {@code body().close()}. */
  @Override public void close() {
    body.close();
  }


  public static class Builder {
    private Request request;
    private Protocol protocol;
    private int code = -1;
    private String message;
    private Handshake handshake;
    private Headers.Builder headers;
    private ResponseBody body;
    ......

    public Builder() {
      headers = new Headers.Builder();
    }

    private Builder(Response response) {
      this.request = response.request;
      this.protocol = response.protocol;
      this.code = response.code;
      this.message = response.message;
      this.handshake = response.handshake;
      this.headers = response.headers.newBuilder();
      this.body = response.body;
      ......
    }

    public Builder request(Request request) {
      this.request = request;
      return this;
    }

    .....
  }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

上面的代码有删简。但我们可以得到一此相关的线索 
* Response是final修饰,所以它不能被继承。 
* Response的构造函数被private修饰,而它内部有一个Builder,也正是因为它的属性太多了,所以很适合用建造者模式去开发代码。 
* Response实现了Closeable接口,所以它有close()方法,而它的close方法中调用了域body的close()方法。 
* Response的code属性代表http的状态码如常见的200。 
* Response的body属性是一个ResponseBody对象,代表消息体。

ResponseBody

上一节中提到Response内部有一个属性body,它是一个ResponseBody,Response关闭时会把它也关闭,这一节我们就来讲ResponseBody.

A one-shot stream from the origin server to the client application with the raw bytes of the response body. Each response body is supported by an active connection to the webserver. This imposes both obligations and limits on the client application.

ResponseBody代表一个从服务端到客户端的一次性流。每个ResponseBody被一个活跃的网络连接支持。这样的机制使得了客户端需要正确使用它,并且也同时对客户端做了一些限制。

ResponseBody消费后必须关闭

每一个ResponseBody背后被一个有限的资源支持着比如一个Socket(网络连接),比如一个打开的文件(缓存文件)。如果ResponseBody关闭失败则会导致内存泄漏或者是程序运行变慢。

ResponseBody和Response都有close()方法,处理完数据后,你可以选择调用下面方法中的任何一个:


Response.close()
Response.body().close()
Response.body().source().close()
Response.body().charStream().close()
Response.body().byteString().close()
Response.body().bytes()
Response.body().string()


   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

ResponseBody只能被消费一次

ResponseBody通常用来对特别大的Response进行流处理,经常的情况是它要代表的数据容量大于系统给程序分配的内容量,甚至是大于设备整个内存。代表例子就是一个网络视频应用。 
因为ResponseBody并没有缓存整个Response的内容。所以,如果要一次访问数据的话,可以调用它的bytes()、string()方法。如果要进行流处理的话就要调用它的source(),bytestream()和charstream().

一次性读取
public final byte[] bytes() throws IOException {
    long contentLength = contentLength();
    if (contentLength > Integer.MAX_VALUE) {
      throw new IOException("Cannot buffer entire body for content length: " + contentLength);
    }

    BufferedSource source = source();
    byte[] bytes;
    try {
      bytes = source.readByteArray();
    } finally {
      Util.closeQuietly(source);
    }
    if (contentLength != -1 && contentLength != bytes.length) {
      throw new IOException("Content-Length and stream length disagree");
    }
    return bytes;
  }


  /**
   * Returns the response as a string decoded with the charset of the Content-Type header. If that
   * header is either absent or lacks a charset, this will attempt to decode the response body as
   * UTF-8.
   */
  public final String string() throws IOException {
    return new String(bytes(), charset().name());
  }



   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

可以看到string()内部调用了bytes()方法。 
bytes()方法,返回的是byte数组。它内部借助于Source()方法得到一个BufferedSource对象,BufferedSource是OkIo开源库的一个接口,这个是另外一个知识点,大家把它理解成一组缓存的数据便是。

通过string()我们可以得到一字符消息。 
通过bytes()我们可以处理字节消息回复。

流的方式读取
public abstract BufferedSource source();


public final InputStream byteStream() {
    return source().inputStream();
  }


/**
   * Returns the response as a character stream decoded with the charset of the Content-Type header.
   * If that header is either absent or lacks a charset, this will attempt to decode the response
   * body as UTF-8.
   */
  public final Reader charStream() {
    Reader r = reader;
    return r != null ? r : (reader = new InputStreamReader(byteStream(), charset()));
  }

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

可以看到charStream()和byteStream()最终调用的是source()方法。刚刚有讲过它最终借助于Okio库的力量返回了一个BuffredSource对象。

通过charStream()返回Reader对象,byteStream()返回InputStream对象。这就回到了我们java世界常见的socket编程模式。我们用Reader读取服务器的文本文件,用InputStream可以读取服务器上的音频、视频、图片等等。所以,volley不适合的文件下载okhttp就这样搞定了。

close方法

官网特别解释了response消费后要关闭responsebody.我们看它代码。

public void close() {
    Util.closeQuietly(source());
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

它最终要关闭一个BuffredSource对象,也就是关闭一个资源。

Request

Response代表回复。现在我们来谈谈请求Request. 
文章前面的例子都有写

Request request = new Request.Builder().url("http://blog.csdn.net/briblue").build();

   
   
  • 1
  • 2
  • 1
  • 2

可以看到Request通过了内部的一个Builder构造,也是应用了建造者模式。 
源码不多,如下:

/**
 * An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself
 * immutable.
 */
public final class Request {
  private final HttpUrl url;
  private final String method;
  private final Headers headers;
  private final RequestBody body;
  private final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.

  private 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;
  }

  public HttpUrl url() {
    return url;
  }

  public String method() {
    return method;
  }

  public Headers headers() {
    return headers;
  }



  public RequestBody body() {
    return body;
  }



  public Builder newBuilder() {
    return new Builder(this);
  }



  ......

  public static class Builder {
    private HttpUrl url;
    private String method;
    private Headers.Builder headers;
    private RequestBody body;
    private Object tag;

    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    private Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      this.body = request.body;
      this.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }

    public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      return this;
    }

    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this
     * exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs.
     */
    public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      // Silently replace websocket URLs with HTTP URLs.
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }

      HttpUrl parsed = HttpUrl.parse(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code
     * https}.
     */
    public Builder url(URL url) {
      if (url == null) throw new NullPointerException("url == null");
      HttpUrl parsed = HttpUrl.get(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

    /**
     * Sets the header named {@code name} to {@code value}. If this request already has any headers
     * with that name, they are all replaced.
     */
    public Builder header(String name, String value) {
      headers.set(name, value);
      return this;
    }

    /**
     * Adds a header with {@code name} and {@code value}. Prefer this method for multiply-valued
     * headers like "Cookie".
     *
     * <p>Note that for some headers including {@code Content-Length} and {@code Content-Encoding},
     * OkHttp may replace {@code value} with a header derived from the request body.
     */
    public Builder addHeader(String name, String value) {
      headers.add(name, value);
      return this;
    }

    public Builder removeHeader(String name) {
      headers.removeAll(name);
      return this;
    }

    /** Removes all headers on this builder and adds {@code headers}. */
    public Builder headers(Headers headers) {
      this.headers = headers.newBuilder();
      return this;
    }

    /**
     * Sets this request's {@code Cache-Control} header, replacing any cache control headers already
     * present. If {@code cacheControl} doesn't define any directives, this clears this request's
     * cache-control headers.
     */
    public Builder cacheControl(CacheControl cacheControl) {
      String value = cacheControl.toString();
      if (value.isEmpty()) return removeHeader("Cache-Control");
      return header("Cache-Control", value);
    }

    public Builder get() {
      return method("GET", null);
    }

    public Builder head() {
      return method("HEAD", null);
    }

    public Builder post(RequestBody body) {
      return method("POST", body);
    }

    public Builder delete(RequestBody body) {
      return method("DELETE", body);
    }

    public Builder delete() {
      return delete(RequestBody.create(null, new byte[0]));
    }

    public Builder put(RequestBody body) {
      return method("PUT", body);
    }

    public Builder patch(RequestBody body) {
      return method("PATCH", body);
    }

    public Builder method(String method, RequestBody body) {
      if (method == null) throw new NullPointerException("method == null");
      if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
      if (body != null && !HttpMethod.permitsRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must not have a request body.");
      }
      if (body == null && HttpMethod.requiresRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must have a request body.");
      }
      this.method = method;
      this.body = body;
      return this;
    }

    ......

    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
  }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203

Request类也是被final修饰,同Response一样通过建造者模式构造。 
Request中有几个重要的属性

  • HttpUrl url;
  • String method;
  • Headers.Builder;
  • RequestBody body;

HttpUrl

这个显然代表网络主机地址。

method

代表数据请求方法类型,如GET和POST.

Headers

代表http请求的header参数的封装。 
Headers对象添加表头 通过它的Buidler对象方法add();

/** Add a field with the specified value. */
public Builder add(String name, String value) {
  checkNameAndValue(name, value);
  return addLenient(name, value);
}


Builder addLenient(String line) {
      int index = line.indexOf(":", 1);
      if (index != -1) {
        return addLenient(line.substring(0, index), line.substring(index + 1));
      } else if (line.startsWith(":")) {
        // Work around empty header names and header names that start with a
        // colon (created by old broken SPDY versions of the response cache).
        return addLenient("", line.substring(1)); // Empty header name.
      } else {
        return addLenient("", line); // No header name.
      }
    }

    /** Add an header line containing a field name, a literal colon, and a value. */
    public Builder add(String line) {
      int index = line.indexOf(":");
      if (index == -1) {
        throw new IllegalArgumentException("Unexpected header: " + line);
      }
      return add(line.substring(0, index).trim(), line.substring(index + 1));
    }


   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

如果获取Headers中的head可以通过get()方法

/** Returns the last value corresponding to the specified field, or null. */
  public String get(String name) {
    return get(namesAndValues, name);
  }

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

RequestBody

代表RequestBody请求的消息体。对于Http知识,我们知道get请求发把参数当作head放在消息头中。而post请求将参数放在消息体中。在okhttp中RequestBody对应了http中的body. 
它的代码如下:

public abstract class RequestBody {
  /** Returns the Content-Type header for this body. */
  public abstract MediaType contentType();

  /**
   * Returns the number of bytes that will be written to {@code out} in a call to {@link #writeTo},
   * or -1 if that count is unknown.
   */
  public long contentLength() throws IOException {
    return -1;
  }

  /** Writes the content of this request to {@code out}. */
  public abstract void writeTo(BufferedSink sink) throws IOException;

  /**
   * Returns a new request body that transmits {@code content}. If {@code contentType} is non-null
   * and lacks a charset, this will use UTF-8.
   */
  public static RequestBody create(MediaType contentType, String content) {

  }

  /** Returns a new request body that transmits {@code content}. */
  public static RequestBody create(final MediaType contentType, final ByteString content) {

  }



  /** Returns a new request body that transmits the content of {@code file}. */
  public static RequestBody create(final MediaType contentType, final File file) {
    if (file == null) throw new NullPointerException("content == null");

    return new RequestBody() {
      @Override public MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return file.length();
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        Source source = null;
        try {
          source = Okio.source(file);
          sink.writeAll(source);
        } finally {
          Util.closeQuietly(source);
        }
      }
    };
  }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

上面的源码有精简。 
RequestBody是一个抽象类,它的创建都是通过create()方法系列,create()方法都需要传入一个MediaType类型。

contentLength()返回消息体长度,如果长度未知返回-1;

write()方法显然是用来向服务器传输数据的。

MeiaType

public final class MediaType 
extends Object

An RFC 2045 Media Type, appropriate to describe the content type of an HTTP request or response body.

MediaType对应http协议中的Content-Type。用来描述请求或者请求中的消息体的内容类型。如mp3,html,txt,json等。

它的源码如下:

public final class MediaType {

......
  private final String mediaType;
  private final String type;
  private final String subtype;
  private final String charset;

  private MediaType(String mediaType, String type, String subtype, String charset) {
    this.mediaType = mediaType;
    this.type = type;
    this.subtype = subtype;
    this.charset = charset;
  }

  /**
   * Returns a media type for {@code string}, or null if {@code string} is not a well-formed media
   * type.
   */
  public static MediaType parse(String string) {

    ......
    return new MediaType(string, type, subtype, charset);
  }

  /**
   * Returns the high-level media type, such as "text", "image", "audio", "video", or
   * "application".
   */
  public String type() {
    return type;
  }

  /**
   * Returns a specific media subtype, such as "plain" or "png", "mpeg", "mp4" or "xml".
   */
  public String subtype() {
    return subtype;
  }

  /**
   * Returns the charset of this media type, or null if this media type doesn't specify a charset.
   */
  public Charset charset() {
    return charset != null ? Charset.forName(charset) : null;
  }



  /**
   * Returns the encoded media type, like "text/plain; charset=utf-8", appropriate for use in a
   * Content-Type header.
   */
  @Override public String toString() {
    return mediaType;
  }

}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

MediaType通过parse()方法,创建对象。 
type()方法返回高级另的类别如”text”,”image”,”audio”,”video”或者”application”。 
而subType()返回的是比较细的类别如”plain”,”png”,”mp4”,”xml”,”json”等等。 
charset()返回字符集。

总结

本篇文章,我们学习了OKHTTP的基础知识,并学习了用OKHTTP编写网络请求代码。涉及到的基础知识点有

  • Call
  • 同步请求 execute
  • 异步请求 enqueue
  • 异步请求时的回调 Callback
  • 服务器的回复 Response
  • 服务的消息体 ResponseBody
  • 网络访问的请求 Request
  • Header
  • 请求的消息体 RequestBody
  • 消息体的数据类型MediaType

熟悉了上面基本的API,我们已经可以比较流畅地运用okhttp库开发相应代码了。最后,再用一段示例代码来避免读者的遗忘。

private void testOKHttp(){

        OkHttpClient  client = new OkHttpClient();
        String url = "http://blog.csdn.net/briblue";
        Request request = new Request.Builder().url(url).build();

        Call call = client.newCall(request);

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

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("好的,我只是用来演示:"+response.body().string());
                //关掉response.body
                response.body().close();
            }
        });

    }

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值