OkHttp

1,002 阅读4分钟

OkHttp是一个高效的HTTP客户端,能有效的提高数据加载的速度,节省带宽。

1. HTTP/2 支持同一主机的所有请求共享同一个socket。

2. 当HTTP/2 不可用时,使用连接池减少请求的延迟。

3. 透明的GZIP 压缩减少下载数据的大小。

4. 缓存响应避免重复的网络请求。

OkHttp 在网络性能不好的情况下能够很好的工作,避免常见的网络连接问题。如果你的HTTP服务有多个IP地址,OkHttp 在第一次连接失败时,会尝试其他的地址。这对于 IPv4+IPv6 以及托管在冗余数据中心的服务来说是必要的。OkHttp 使用TLS 特性 (SNI, ALPN)初始化HTTP连接,并在握手失败时降级使用TSL1.0 尝试初始化连接。

OkHttp支持同步和异步请求,在 Android 2.3版本,Java 1.7 版本之上可以使用。

一、 基本使用


1. 创建OkHttpClient 

//使用默认的设置参数创建OkHttpClient
public final OkHttpClient mClient = new OkHttpClient()

//使用Builder自定义设置
 public final OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(new HttpLoggingInterceptor())
            .cache(new Cache(cacheDir, cacheSize))
            .readTimeout(5, TimeUnit.SECONDS)
            .build();

由于每个OkHttpClient 对象都持有自己的线程池和连接池,所以应用中应该创建一个client 对象,然后所有的HTTP 请求复用这个对象发送请求,从而减少网络延迟,节省内存。

使用newBuilder() 方法可以个性化共享的OkHttpClient, 这个client 和原有client可以共享连接池和线程池。

OkHttpClient eagerClient = client.newBuilder()
        .readTimeout(500, TimeUnit.MILLISECONDS)
        .build();

默认OkHttpClient 是不需要Shutdown,持有的线程和连接会在空闲时自动释放。

//关闭线程池
client.dispatcher().executorService().shutdown();
//关闭连接池
client.connectionPool().evictAll();
//关闭缓存
client.cache().close();

停止线程池,以后发送到这个client 的Call 将直接被丢弃; 关闭缓存时如果正在创建Call,会导致Crash 。

2. 同步请求:

public void syncRequest() {
        //创建Request对象
        Request mRequest = new Request.Builder()
                .url("http://www.baidu.com")
                .get()
                .build();

        //创建Call 对象
        Call mCall = mClient.newCall(mRequest);

        //调用Call 的execute()发送同步请求
        try {
            Response response = mCall.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
}

发送同步请求后,就会进入阻塞状态,直到收到响应。

3. 异步请求

public void asyncRequest() {
    //创建Request对象
    Request request = new Request.Builder()
            .url("http://www.baidu.com")
            .get()
            .build();

    //将Request 封装成Call 对象
    Call call = mClient.newCall(request);

    //调用Call的enqueue方法进行异步请求
    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());
            //子线程返回
        }
    });
}



4. POST请求

1)post提交json数据

import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class PostExample {
    //声明 MediaType 为JSON
    public static final MediaType JSON 
            = MediaType.parse("application/json; charset=utf-8");

    final OkHttpClient mOkHttpClient = new OkHttpClient();

    String post(String url, String data) throws IOException {

        //创建 RequestBody对象
        RequestBody body = RequestBody.create(JSON, data);

        //通过post()方法添加requstbody
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        //发送请求
        try (Response response = mOkHttpClient.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);
    }
}

2) 提交key-value

String post(String url) throws IOException {

    RequestBody body = new FormBody.Builder()
            .add("platform", "android")
            .addEncoded("site", URLEncoder.encode("https://www.baidu.com/"))
            .build();

    Request request = new Request.Builder()
            .url(url)
            .post(body)
            .build();

    try (Response response = mOkHttpClient.newCall(request).execute()) {
        return response.body().string();
    }
}

RequestBody是一个抽象类,提供了提交 String字符串,byte[] 字节数组, File文件的静态方法,可以返回一个RequestBody对象,

public static RequestBody create(MediaType contentType, String content)

public static RequestBody create(MediaType contentType, byte[] content)

public static RequestBody create(MediaType contentType, byte[] content,
                                  int offset, int byteCount) 

public static RequestBody create(MediaType contentType, final File file)

FormBody继承自RequestBody,提供了Builder类来创建FormBody对象;同时还有MultipartBody也继承了RequestBody,并提供了Builder返回一个MultipartBody对象。

5. ResponseBody 与 Response

每个响应体都由一个有限的资源支持,比如套接字(网络请求)或打开的文件(用于缓存的响应)。

ResponseBody 和 Response都实现了Closeable 接口,如果使用了Call.execute() 或者实现了Callback.onResponseI() 一定要close它,可以通过下面的任意方法关闭:

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

或者使用带资源的try语句:

  Call call = client.newCall(request);
  try (Response response = call.execute()) {
     ... // Use the response.
   }

   Call call = client.newCall(request);
   call.enqueue(new Callback() {
     public void onResponse(Call call, Response response) throws IOException {
       //带资源的try语句保证try块退出或存在异常时,都会自动调用res.close()
       try (ResponseBody responseBody = response.body()) {
         ... // Use the response.
       }
     }

     public void onFailure(Call call, IOException e) {
       ... // Handle the failure.
     }
   });

bytes()方法,string()方法会将 Response全部加载到内存,source()方法,byteStream()方法,charStream()会按流读入,不会将整个响应读入内存,可以读取比较大的响应;

二、 请求执行流程和源码分析

1. OkHttpClinet 初始化