简介

Retrofit 是 Square 公司开发的一款针对 Android 网络请求的框架,对网络认证 REST API 进行了很好对支持此,网络请求的本质是 OkHttp 完成的, Retrofit 只是进负责网络请求接口的封装

  • 基于 OkHttp 的封装,
  • 使用面向接口的方式进行网络请求,
  • 利用动态生成的代理类封装了网络接口请求的底层,
  • 利用注解来方便拼装请求参数(比如 headers 或者 params ,就是通过注解,然后拼接到网络请求中)
  • 对于返回结果也可以完美适配 Rxjava ,如果用的其他结构的数据,也可以自己写相对应的 Covers 来解析。

总而言之。Retrofit是基于 OkHttp 的封装,更加方便利用 OkHttp 的使用。 注:

  1. OkHttp现在已经得到 Google 官方认可,大量的 app 都采用 OkHttp 做网络请求,底层使用的还是 HttpUrlconnection 。
  2. 更详细 OkHttp 解释请参考 扫盲系列 - Retrofit 基本用法

REST

既然 Retrofit 是一个 RESTful 的 HTTP 网络请求的框架的封装,那么什么是 RESTful 架构呢?

REST: REpresentational State Transfer的意思 ,是一组架构约束条件和原则

RESTful 都满足以下条件

  1. 每一个 URL 代表一个资源
  2. 客户端和服务器之间,传递这种资源的某种表现层
  3. 客户端通过四个 HTTP 动词,对服务端资源进行操作,实现表层状态转换

更多关于 REST 的介绍:什么是REST - GitHub讲解的非常详细

优缺点

优点

  • 可以配置不同 HTTP client 来实现网络请求,如OkHttp、Httpclient等
  • 请求的方法参数注解都可以定制
  • 支持同步、异步和RxJava
  • 超级解耦
  • 可以配置不同的反序列化工具来解析数据,如json、xml等
  • 使用非常方便灵活
  • 框架使用了很多设计模式(感兴趣的可以看看源码学习学习)

缺点:

  • 不能接触序列化实体和响应数据
  • 执行的机制太严格
  • 使用转换器比较低效
  • 只能支持简单自定义参数类型

请求网络的流程

几种网络框架的对比

几种网络框架的对比

使用介绍:

大致分为 7 步,这里面会有一个实例应用。以获取豆瓣 Top250 榜单

  1. 添加 Retrofit 依赖库
  2. 创建接收器返回的数据类
  3. 创建用于描述网络请求的接口
  4. 创建 Retrofit 实例
  5. 创建网络请求接口实例并配置网络请求参数
  6. 发送网络请求(异步/同步)
  7. 处理服务器返回的数据

1.添加 Retrofit 依赖库

这个就是没什么可说的了,就是一些 Retrofit 所需要的库,一般会添加下面几个库

  compile 'com.squareup.retrofit2:retrofit:2.1.0'//retrofit
  compile 'com.squareup.OkHttp3:OkHttp:3.1.2'

  compile 'com.google.code.gson:gson:2.6.2'//Gson 库
  //下面两个是 RxJava 和RxAndroid
  compile 'io.reactivex:rxjava:1.1.0'
  compile 'io.reactivex:rxandroid:1.1.0'
  compile 'com.squareup.retrofit2:converter-gson:2.1.0'//转换器,请求结果转换成Model
  compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'//配合 Rxjava 使用

说明:

  1. 因为 retrofit Retrofit 是基于 OkHttp 的封装的,所以肯定需要 OkHttp 的库
  2. 因为网络请求通常用到解析 JSON ,这里适用 Gson 库解析,以及 Retrofit 中转换成 JavaBean 的转换器converter-gson,当然,还有其他的转换器可以使用。
  3. 因为我们经常会把 Retrofit 和 RxJava 配合使用,所以可能还会用到 RxJava 相应的库
  4. 当然,需要添加网络权限

2:创建接收器返回的数据类

其实就是一些 JavaBean ,用于保存返回来的数据,例如

public class MovieObject {
  public int count;
  public int start;
  public int total;
  public List<Movie> subjects;
  public String title;
}

public class Movie {
  public Rate rating;
  public String title;
  public String collect_count;
  public String original_title;
  public String subtype;
  public String year;
  public MovieImage images;

  public static class Rate{
    public int max;
    public float average;
    public String stars;
    public int min;
  }

  public static class MovieImage{
    public String small;
    public String large;
    public String medium;
  }

  @Override
  public String toString() {
    return "Movie{" + "title='" + title + '\'' + ", subtype='" + subtype + '\'' +  '}';
  }
}

3:创建用于描述网络请求的接口

Retrofit 把 HTTP 请求抽象成一个 Java 接口,采用注解方式描述和配置网络请求参数

  • 使用动态代理方式将该接口的注解翻译成一个 HTTP 请求,最后再执行 HTTP 请求
  • 接口中的每个方法都需要使用注解,否则会报错。
public interface MovieService {
    //获取豆瓣 Top250 榜单
    @GET("top250")
    Call<MovieObject> getTop250(@Query("start") int start, @Query("count") int count);
}

解释:

  1. @GET("top250") 就是使用 get 方式发送请求, top250 是一个尾地址,我们一般访问网络,都会有一个基本地址,例如“https://api.douban.com/v2/movie/”, 后边再拼一个字符串(例如top250),形成当前接口的完整地址,所以该接口的完整地址就是 “https://api.douban.com/v2/movie/top250”, 当然,也可以直接使用绝对路径。例如@GET("https://api.douban.com/v2/movie/top250"),这样就不需要设置基本路径了。
  2. Call<MovieObject> Call 就是一种返回类型, MovieObject 是我们上一步定义的实体类
  3. getTop250 这个是方法名,就不解释了
  4. 接下来看方法中的参数,@Query("start") int start,这种方式挺怪哦,之前没见到过,@Query标签 ,表示请求参数,将会以key=value的方式拼接在 url 后面,例如 url = http://www.println.net/?cate=android, 其中,Query = cate 如果参数多的话可以用@QueryMap标签,接收一个Map

网络请求接口注解类型

我们指定,网络请求有其中方式,除了常见的 get , post ,还有 put , delete , path , head , options ,所以 Retrofit 为其对应了相应的注解

重点说一下 @HTTP,这个注解, HTTP 注解则可以代替以上方法中的任意一个注解,有 3 个属性:method、path, hasBody ,下面是用 HTTP 注解实

    /**
     * method 表示请求的方法,区分大小写
     * path表示路径 默认为"",值为请求的相对 URL 路径
     * hasBody 表示是否有请求体,默认为false
     * {id} 表示是一个变量
     */
    @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
    Call<ResponseBody> getBlog(@Path("id") int id);

method 的值 retrofit 不会做处理,所以要自行保证其准确性。 接下来说一下网络请求参数,也就是刚才看到的那些方法中的参数注解。

网络请求参数

@Header & @Headers

具体使用如下:

// @Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()

以上的效果是一致的。区别在于:

  • @Headers 作用于方法, 用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在
  • @Header 作用于方法的参数,用于添加不固定值的 Header ,该注解会更新已有的请求头

@Body

  • 作用: 以 post 形式 传递 自定义数据类型给服务器。
  • 特别注意: 如果提交的是一个 Map ,那么作用相当于@Field , 不过 Map 要经过FromBody.Builder 类处理成符合 OkHttp 格式的表单,如
FormBody.Builder builder=new FormBody.Builder();
builder.add("key","value");

可以传递 json 串 例如:

@POST("add")
Call<List<User>> addUser(@Body User user);

// 使用方法
Call<List<User>> call = userBiz.addUser(new User(1001, "jj", "123,", "jj123", "jj@qq.com"));

@Field & @FieldMap

  • 作用: 发送 Post 请求的时候,提交请求的表单字段。
  • 格式如下:
    public interface GetRequest_Interface {
      @POST("/form")
      @FormUrlEncoded
      Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
    
       // Map的 key 作为表单的键
      @POST("/form")
      @FormUrlEncoded
      Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
    }
    

注意:

  1. 必须加上 @FormUrlEncoded标签,否则会抛异常
  2. 参数标签用 @Field 或者 @Body 或者 @FieldMap
  3. 使用 POST 方式时,必须要有参数,否则会抛异常

具体使用:

// @Field
Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);

// @FieldMap
// 实现的效果与上面相同,但要传入Map
Map<String, Object> map = new HashMap<>();
map.put("username", "Carson");
map.put("age", 24);
Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);

@Part & @PartMap

  • 作用: 发送 Post 请求的时候,提交请求的表单字段,尽管与@Field & @FieldMap功能相同,但是它携带的数据类型更加丰富,包括数据流,所以适用于有文件上传的场景

  • 格式如下:与 @Multipart 注解配合使用

@POST("/from")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") ResponseBody nameBody
                                  , @Part("age") ResponseBody ageBody
                                  , @Part MultipartBody.Part file);

@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args
                                    , @Part MultipartBody.Part file);

@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);

解释:

  1. Part 后面支持三种类型, RequestBody ,MultipartBody.Part,任意类型
  2. 除了 MultipartBody.Part 以外,其他类型必须带上表单字段,因为 MultipartBody.Part 中已经包含了表单字段的信息
  3. PartMap 注解支持一个 Map 作为参数。支持 RequestBody 类型,如果还有其他类型,会被 retrofit2.Converter 转换,使用 Gson GsonRequestBodyConverter ,所以 MultipartBody.Part 就不适用了,文件只能用 @Part MultipartBody.Part

具体使用:

MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "Carson");
RequestBody age = RequestBody.create(textType, "24");
RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");

// @Part
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
Call<ResponseBody> call3 = service.testFileUpload1(name, age , filePart);
ResponseBodyPrinter.printResponseBody(call3);

// @PartMap
// 实现和上面同样的效果
Map<String, RequestBody> fileUpload2Args = new HashMap<>();
fileUpload2Args.put("name", name);
fileUpload2Args.put("age", age);
//这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有
//fileUpload2Args.put("file", file);
Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件
ResponseBodyPrinter.printResponseBody(call4);

@Query & @QueryMap

  • 作用:用于 @GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value),可以动态设置请求参数, 如:url = http://www.println.net/?cate=android, 其中,Query = cate

  • 具体使用:配置时只需要在接口方法中增加一个参数即可:

    @GET("/")    
    Call<String> cate(@Query("cate") String cate);
    

@Path

  • 作用: URL地址的缺省值/默认值
  • 具体使用:
    @GET("users/{user}/repos")
    Call<ResponseBody>  getBlog@Path("user") String user ;
    // 访问的 API 是:https://api.github.com/users/{user}/repos
    // 在发起请求时, {user} 会被替换为方法的第一个参数 user(被@Path注解作用)
    

@Url

  • 作用:直接传入一个请求 URL 变量,用于 URL 设置
  • 注意: 不要以 / 开头
  • 具体使用:
    @GET //当有 URL 注解时,@GET传入的 URL 就可以省略
    Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
    // 当GET、POST...HTTP等方法中没有设置 Url 时,则必须使用  @Url提供
    

标记

@FromUrlEncoded

  • 作用: 发送from-encoded 的数据 ,对应content-type=application/x-www-form-urlencoded。
  • 每个键值对需要用@Field来注解键名,随后的对象需要提供值

@Multipart

  • 作用: 表示发送form-encoded 的数据,适用于有文件上传的场景,对应Content-Type: multipart/form-data
  • 每个键值对需要使用@Part来注解键名,随后的对象需要提供值

@Streaming

如果下载一个非常大的文件, Retrofit 会试图将整个文件读进内存,这是绝对不能发生的事情,为了避免这种情况,我们就需要添加@Streaming 注解来声明请求

@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);  

声明@Streaming 意味着立刻传递字节码,而不需要把整个文件读进内存。值得注意的是,如果你使用了@Streaming,并且依然使用以上的代码片段来进行处理。 Android 将会抛出android.os.NetworkOnMainThreadException异常。 因此,需要把这些操作放到一个单独的工作线程中。例如 AsyncTaks

final FileDownloadService downloadService =  ServiceGenerator.create(FileDownloadService.class);

new AsyncTask<Void, Long , Void>() {  
   @Override
   protected Void doInBackground(Void... voids) {
       Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);
       call.enqueue(new Callback<ResponseBody>() {
           @Override
           public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
               if (response.isSuccess()) {
                   Log.d(TAG, "server contacted and has file");
                   boolean writtenToDisk = writeResponseBodyToDisk(response.body());
                   Log.d(TAG, "file download was a success? " + writtenToDisk);
               }
               else {
                   Log.d(TAG, "server contact failed");
               }
           }
       return null;
   }
}.execute();

4: 创建 Retrofit 实例

Retrofit retrofit = new Retrofit.Builder()//
                .baseUrl("http://fanyi.youdao.com/") // 设置网络请求的 Url 地址,这一般是基本路径,
                .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持 RxJava 平台
                .build();

baseUrl() 里面的路径一般都是基本路径, 并且总是以 /结尾

关于数据解析器

Retrofit 支持多种数据解析方式。使用的时候只需要添加相应的依赖库就可以了

数据解析器 Gradle 库
Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson com.squareup.retrofit2:converter-jackson:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
Moshi com.squareup.retrofit2:converter-moshi:2.0.2
Wire com.squareup.retrofit2:converter-wire:2.0.2
Scalars com.squareup.retrofit2:converter-scalars:2.0.2

好像除了 Gson 和 Jackson 见过,其他都没见过(有点小丢人)

关于网络适配器 (CallAdapter)

Call 都是使用封装的 Java 对象作为泛型,但是请求返回的数据均是 Json 类型, Retrofit 并不支持此格式数据与 API 进行通信。如果想实现此操作,应添加相应的适配器。

  • Retrofit 支持多种网络请求适配方式, guava , Java8 和RxJava 使用时如果使用的是 Android 默认的 CallAdapter ,则不需要添加网络适配器的依赖,否则需要按照需求进行添加 Retrofit 提供的CallAdapter

  • 使用时需要添加的 Gradle 依赖库

网络请求适配器 Gradle 库
guava com.squareup.retrofit2:adapter-guava:2.0.2
Java8 com.squareup.retrofit2:adapter-java8:2.0.2
RxJava com.squareup.retrofit2:adapter-rxjava:2.0.2

这三个好像也只用过 RxJava ,还是前两天才了解开始玩的

5:创建网络请求接口实例

//获取接口实例
MovieService movieService = retrofit.create(MovieService.class);
//调用 getTop250() 得到一个 Call 对象
Call<MovieObject> call = movieService.getTop250(0, 20);
  • 调用方法得到一个 call Call 其实在 Retrofit 中就是行使网络请求并处理返回值的类,
  • 调用的时候会把需要拼接的参数传递进去,此处最后得到的 url 完整地址为 https://api.douban.com/v2/movie/top250

6:发送网络请求(同步/异步),并处理返回结果

//发送异步网络请求
call.enqueue(new Callback<MovieObject>() {
      @Override  //请求成功时回调
      public void onResponse(Call<MovieObject> call, Response<MovieObject> response) {
                //请求处理,输出结果
        MovieObject body = response.body();
        Log.e("hoyouly", "onResponse: " + body.subjects.toString());
      }

      @Override  //请求失败时候的回调
      public void onFailure(Call<MovieObject> call, Throwable t) {
        t.printStackTrace();
        Log.e("hoyouly", getClass().getSimpleName() + " -> onFailure: " + t.getMessage());
      }
    });

// 发送网络请求(同步)
Response<MovieObject> response = call.execute();

注意:同步请求是耗时操作,且执行时是在当前线程执行。若在 UI 线程执行同步请求,此时会阻塞 UI 线程。耗时过长,容易造成 ANR 。故执行同步请求时,需自行开辟线程,以免造成 ANR 。

取消请求

使用 cancel() 方法, call.cancel();

7: 处理返回结果

主要是 通过 response 类的 body()对返回的数据进行处理 参照步骤 6 中的处理

Retrofit 本质流程

拦截器

addNetworkInterceptor() 添加的是网络拦截器(Network Interfacetor),它会在 request 和 response 时分别被调用一次;
addInterceptor() 添加的是应用拦截器(Application Interceptor),只会在 response 被调用一次。

增加日志信息

retrofit2.0中是没有日志功能的。但是retrofit2.0中依赖 OkHttp ,所以可以通过 OkHttp 中的 interceptor 来实现实际的底层的请求和响应日志。在这里我们需要修改上一个 retrofit 实例,为其自定自定义的 OkHttpClient 。并使用 addNetworkInterceptor() 方法添加到 OkHttpClient 中

使用HttpLoggingInterceptor

HttpLoggingInterceptor httpLoggingInterceptor=new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient okHttpClient=new OkHttpClient.Builder()
    .addInterceptor(httpLoggingInterceptor)//添加拦截
    .build();

Retrofit retrofit=new Retrofit.Builder()//
    .baseUrl("http://fanyi.youdao.com/")//
    .client(okHttpClient)//
    .addConverterFactory(GsonConverterFactory.create())//
    .build();

注意:

  1. 使用的 OkHttp 版本和 logging-interceptor 版本必须得一直,并且需要不小于3.5.0 我刚开始都是使用的是3.1.2 版本,结果报错了,
java.lang.NoSuchMethodError:
      No virtual method log(Ljava/lang/String;)V in class Lokhttp3/internal/Platform;
      or its super classes (declaration of 'okhttp3.internal.Platform' appears in /data/app/top.hoyouly.framework-1/base.apk)

改成3.5.0 版本就正常了, JakeWharton 大神说的,地址 :JakeWharton的回答

compile 'com.squareup.okhttp3:okhttp:3.5.0'

//添加打印 log 的拦截器
compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'

自定义拦截器

  1. 需要实现 Interceptor 接口,复写intercept(Chain chain)方法,并返回 Response
  2. Request 和 Response 的 Builder 中有 header() , addHeader() , headers() 方法,需要注意的是使用 header() 有重复的将会被覆盖,而 addHeader() 则不会。
private class LogInterceptor implements Interceptor{
  @Override
  public okhttp3.Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    long t1 = System.nanoTime();
    Log.d("hoyouly", "HttpHelper1" + String.format("Sending request %s on %s%n%s"
                                    , request.url(), chain.connection(), request.headers()));

    okhttp3.Response response = chain.proceed(request);
    long t2 = System.nanoTime();

    Log.d("hoyouly", "HttpHelper2" + String.format("Received response for %s in %.1fms%n%s"
                          , response.request().url(), (t2 - t1) / 1e6d, response.headers()));
    return response;
  }
}
//使用
LogInterceptor logInterceptor=new LogInterceptor();
OkHttpClient okHttpClient=new OkHttpClient.Builder()
    .addInterceptor(logInterceptor)//添加拦截
    .build();

Retrofit retrofit=new Retrofit.Builder()//
    .baseUrl("http://fanyi.youdao.com/")//
    .client(okHttpClient)//
    .addConverterFactory(GsonConverterFactory.create())//
    .build();

下面是一个 OAuth 认证的 Interceptor

public class OAuthInterceptor implements Interceptor {

  private final String username;
  private final String password;

  public OAuthInterceptor(String username, String password) {
    this.username = username;
    this.password = password;
  }

  @Override
  public Response intercept(Chain chain) throws IOException {
    String credentials = username + ":" + password;
    String basic = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);

    Request originalRequest = chain.request();
    String cacheControl = originalRequest.cacheControl().toString();

    Request.Builder requestBuilder = originalRequest.newBuilder()
        //Basic Authentication,也可用于 token 验证, OAuth 验证
        .header("Authorization", basic)
        .header("Accept", "application/json")
        .method(originalRequest.method(), originalRequest.body());

    Request request = requestBuilder.build();

    Response originalResponse = chain.proceed(request);
    Response.Builder responseBuilder =
    //Cache control设置缓存
    originalResponse.newBuilder().header("Cache-Control", cacheControl);

    return responseBuilder.build();
  }
}

请求头拦截器

使用 addInterceptor() 方法添加到 OkHttpClient 中

请求头拦截器是为了让服务器更好的识别该请求,服务器通过请求头来判断该请求是否有效等。

/**
 * 拦截器Interceptors
 * 使用 addHeader() 不会覆盖之前设置的 header ,若使用 header() 则会覆盖之前的header
 */
public static Interceptor getRequestHeader() {
    Interceptor headerInterceptor = new Interceptor() {

        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            Request originalRequest = chain.request();
            Request.Builder builder = originalRequest.newBuilder();
            builder.addHeader("version", "1");
            builder.addHeader("time", System.currentTimeMillis() + "");

            Request.Builder requestBuilder = builder.method(originalRequest.method(), originalRequest.body());
            Request request = requestBuilder.build();
            return chain.proceed(request);
        }

    };
    return headerInterceptor;
}

统一请求拦截器

使用 addInterceptor() 方法添加到 OkHttpClient 中 通过响应拦截器实现了从响应中获取服务器返回的time

public static Interceptor getResponseHeader() {
    Interceptor interceptor = new Interceptor() {

        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            okhttp3.Response response = chain.proceed(chain.request());
            String timestamp = response.header("time");
            if (timestamp != null) {
                //获取到响应 header 中的time
            }
            return response;
        }
    };
    return interceptor;
}

缓存拦截器

使用 okhttp 缓存的话,先要创建 cache ,然后在创建缓存拦截器 创建cache

File httpCacheDirectory=new File(getCacheDir(),"OkHttpCache");
Cache cache=new Cache(httpCacheDirectory,10*1024*1024);

OkHttpClient okHttpClient=new OkHttpClient.Builder()
      .addNetworkInterceptor(getCacheInterceptor())//添加拦截
      .addInterceptor(getCacheInterceptor())
      .cache(cache)
      .build();

创建缓存拦截器

public Interceptor getCacheInterceptor(){
    Interceptor cacheInterceptor= new Interceptor() {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            if (!NetworkUtils.isConnected()) {
                //无网络下强制使用缓存,无论缓存是否过期,此时该请求实际上不会被发送出去。
                request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
            }
            okhttp3.Response response = chain.proceed(request);
            if (NetworkUtils.isConnected()) {//有网络情况下,根据请求接口的设置,配置缓存。
                //这样在下次请求时,根据缓存决定是否真正发出请求。
                String cacheControl = request.cacheControl().toString();
                //当然如果你想在有网络的情况下都直接走网络,那么只需要将其超时时间这是为 0 即可
                //即:String cacheControl="Cache-Control:public,max-age=0"
                int maxAge = 60 * 60; // read from cache for 1 minute
                return response.newBuilder()
                        //.header("Cache-Control", cacheControl)
                        .header("Cache-Control", "public, max-age=" + maxAge)
                        .removeHeader("Pragma")
                        .build();
            } else {
                //无网络
                int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
                return response.newBuilder()
                        .header("Cache-Control", "public,only-if-cached,max-stale=" + maxStale)
                        .removeHeader("Pragma")
                        .build();
            }
            return response;
        }
    };
    return  cacheInterceptor;
}

自定义CookieJar

okHttpClient.cookieJar(new CookieJar() {
  final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();

    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        cookieStore.put(url, cookies);//保存cookie
            //也可以使用 SP 保存
        }

    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        List<Cookie> cookies = cookieStore.get(url);//取出cookie
 return cookies != null ? cookies : new ArrayList<Cookie>();
        }
    });

暂时完结。。。


搬运地址:

Android 你必须了解的网络框架 Retrofit2.0

Retrofit2 完全解析 探索与 OkHttp 之间的关系

这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)

Retrofit 2.0 使用总结

Android 手把手教你使用Retrofit2

【译】Retrofit 2 - 如何从服务器下载文件 c07e8)