OkHttp 使用详解

2019-01-09  本文已影响65人  孤独的根号十二

资料

github源码

资源文件

OkHttp
Okio

gradle配置

implementation 'com.squareup.okhttp3:okhttp:3.10.0'

OkHttp优点

OkHttp扮演着传输层的角色。
1.支持SPDY,可以合并多个到同一个主机的请求
2.使用Okio来大大简化数据的访问与存储,Okio是一个增强 java.io 和 java.nio的库
3.支持连接池,gziping,缓存
4.还处理了代理服务器问题和SSL握手失败问题
从Android4.4开始HttpURLConnection的底层实现采用的是okHttp

使用:

1.初始化OkHttpClient

  int cacheSize = 10 * 1024 * 1024; // 10 MiB
 Cache cache = new Cache(cacheDirectory, cacheSize);
 OkHttpClient client = new OkHttpClient();
 client.setCache(cache);
 client.setConnectTimeout(10, TimeUnit.SECONDS);
    client.setWriteTimeout(10, TimeUnit.SECONDS);
    client.setReadTimeout(30, TimeUnit.SECONDS);
//可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持。
//你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600

推荐让 OkHttpClient 保持单例,用同一个 OkHttpClient 实例来执行你的所有请求,因为每一个 OkHttpClient 实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源

2.构建RequestBody 和Request

//构造 RequestBody 需要指定MediaType,用于描述请求/响应 body 的内容类型
//提交json数据
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, json);

//提交表单
RequestBody formBody = new FormBody.Builder()
    .add("platform", "android")
    .add("name", "bug")
    .add("subject", "XXXXXXXXXXXXXXX")
    .build();

//提交数据流,其他提交方式最后也都是转换为流
RequestBody requestBody = new RequestBody() {
    @Nullable
    @Override
    public MediaType contentType() {
        return MediaType.parse("text/x-markdown; charset=utf-8");
    }

    @Override//BufferedSink 是Okio里的类,okio是对io和nio的操作优化工具包
    public void writeTo(BufferedSink sink) throws IOException {
         sink.writeUtf8("abckdcd");
    }
};
//提交文件
MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
File file = new File("abc.txt");
RequestBody body=RequestBody.create(mediaType, file)

//提交字符串
MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
String requestBody = "I am Jdqm.";
RequestBody body=RequestBody.create(mediaType, requestBody)

//提交分块请求
//多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求
MultipartBody body = new MultipartBody.Builder("AaB03x")
            .setType(MultipartBody.FORM)
            .addPart(
                    Headers.of("Content-Disposition", "form-data; name=\"title\""),
                    RequestBody.create(null, "Square Logo"))
            .addPart(
                    Headers.of("Content-Disposition", "form-data; name=\"image\""),
                    RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-
                square.png")))
            .build();




Request request = new Request.Builder()
.url(url)   //设置请求的Url
.header("User-Agent", "OkHttp Headers.java")//如果已经有值,旧的将被移除
.addHeader("Accept", "application/json; q=0.5")//可以添加多值
 .addHeader("Accept", "application/vnd.github.v3+json")
.post(body)  //不写post请求,则默认是get请求
.build();

3.发起请求(两种方式,同步和异步)

 final Call call = client.newCall(request);
call.cancle()//取消一个
 //同步请求
Response response = client.newCall(request).execute()
 //异步请求

client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Request request, Throwable throwable) {
        throwable.printStackTrace();
      }
 
      @Override public void onResponse(Response response) throws IOException {
    
        System.out.println(response.body().string());
      }
    });
  if(response.isSuccessful()){ //判断请求是否成功
  //  response.body().string()
   // response.body().byteStream() //对于超过1MB的响应body,应使用流的方式来处理body
  response.body().bytes()
   }else{

   }

缓存

根据是否需要重新向服务器发起请求来分类,可以将其分为两大类(强制缓存,对比缓存),强制缓存如果生效,不需要再和服务器发生交互,而对比缓存不管是否生效,都需要与服务端发生交互,两类缓存规则可以同时存在,强制缓存优先级高于对比缓存

强制缓存

HTTP 1.1 引入 Cache-Control 响应头参数,用于控制缓存,常见的取值有private、no-cache、max-age、must- revalidate等
private :响应只能够作为私有的缓存,默认。
no-cache:每次访问,都刷新,实时向服务器端请求资源 。
max-age:设置缓存最大的有效时间,过完指定时间后再访问刷新
no-store:响应不缓存,不写进磁盘中,基于某些安全考虑。
must-revalidate :响应在特定条件下会被重用,以满足接下来的请求,但是它必须到服务器端去验证它是不是仍然是最新的。
proxy-revalidate :类似于 must-revalidate,但不适用于代理缓存.


缓存流程.png

对比缓存

对比缓存,顾名思义,需要进行比较判断是否可以使用缓存。
浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。
再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。

对于对比缓存来说,缓存标识的传递是我们着重需要理解的,它在请求header和响应header间进行传递,一共分为两种标识传递

Last-Modified/If-Modified-Since规则:

Last-Modified:
服务器在响应请求时,告诉浏览器资源的最后修改时间。

If-Modified-Since:
再次请求服务器时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。
服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。
若资源的最后修改时间大于If-Modified-Since,说明资源又被改动过,则响应整片资源内容,返回状态码200;
若资源的最后修改时间小于或等于If-Modified-Since,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。

Etag/If-None-Match规则:

Etag:
服务器资源的唯一标识符, 浏览器可以根据ETag值缓存数据, 节省带宽. 如果资源已经改变, etag可以帮助防止同步更新资源的相互覆盖. ETag 优先级比 Last-Modified 高.

If-None-Match:
再次请求服务器时,通过此字段通知服务器客户段缓存数据的唯一标识。
服务器收到请求后发现有头If-None-Match 则与被请求资源的唯一标识进行比对,
不同,说明资源又被改动过,则响应整片资源内容,返回状态码200;
相同,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。

看到这里,你也许会问,既然已经有了 Last-Modified 已经能够知道本地缓存是否是最新的了,为什么还需要 Etag 呢?
主要是基于以下几个原因:Last-Modified 标注的最后修改时间只能精确到秒,如果有些资源在一秒之内被多次修改的话,他就不能准确标注文件的新鲜度了如果某些资源会被定期生成,当内容没有变化,但 Last-Modified 却改变了,导致文件没使用缓存有可能存在服务器没有准确获取资源修改时间,或者与代理服务器时间不一致的情形。

HTTP AUTH

使用HTTP AUTH需要在server端配置http auth信息(一般是webserver启动的时候从配置文件里面读取相关信息)。我用中文简述一下http auth的过程:
客户端发送http请求
服务器发现配置了http auth,于是检查request里面有没有"Authorization"的http header
如果有,则判断Authorization里面的内容是否在用户列表里面,Authorization header的典型数据为"Authorization: Basic jdhaHY0=",其中Basic表示基础认证, jdhaHY0=是base64编码的"user:passwd"字符串。
如果没有,或者用户密码不对,则返回http code 401页面给客户端
标准的http浏览器在收到401页面之后,应该弹出一个对话框让用户输入帐号密码;并在用户点确认的时候再次发出请求,这次请求里面将带上Authorization header
使用http auth的场景不会用cookie,也就是说每次都会送帐号密码信息过去。然后我们都知道base64编码基本上等于明文。这削弱了安全。
由于种种缺点,http auth现在用的并不多。不过在路由器等场合还是有应用的,原因是http auth最简单,使用起来几乎是零成本。

Okhttp中的实现:

client.setAuthenticator(new Authenticator() {
      @Override public Request authenticate(Proxy proxy, Response response) {
        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();
      }
 
      @Override public Request authenticateProxy(Proxy proxy, Response response) {
        return null; // Null indicates no attempt to authenticate.
      }
    });

拦截器-interceptor

OkHttp的拦截器链可谓是其整个框架的精髓,用户可传入的 interceptor 分为两类:
一类是全局的 interceptor
另外一类是非网页请求的 interceptor ,这类拦截器只会在非网页请求中被调用,并且是在组装完请求之后,真正发起网络请求前被调用

自定义的拦截器:

 client.interceptors().add(LoggingInterceptor())
public class LoggingInterceptor implements Interceptor {
    private static final String TAG = "LoggingInterceptor";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
//DO SOMETHING

        Response response =  chain.proceed(request);

//DO SOMETHING

        return response;
    }
}
OKHttp的请求流程图.png
上一篇 下一篇

猜你喜欢

热点阅读