扫盲系列 - Retrofit 基本用法
简介
Retrofit 是 Square 公司开发的一款针对 Android 网络请求的框架,对网络认证 REST API 进行了很好对支持此,网络请求的本质是 OkHttp 完成的, Retrofit 只是进负责网络请求接口的封装
- 基于 OkHttp 的封装,
- 使用面向接口的方式进行网络请求,
- 利用动态生成的代理类封装了网络接口请求的底层,
- 利用注解来方便拼装请求参数(比如 headers 或者 params ,就是通过注解,然后拼接到网络请求中)
- 对于返回结果也可以完美适配 Rxjava ,如果用的其他结构的数据,也可以自己写相对应的 Covers 来解析。
总而言之。Retrofit是基于 OkHttp 的封装,更加方便利用 OkHttp 的使用。 注:
- OkHttp现在已经得到 Google 官方认可,大量的 app 都采用 OkHttp 做网络请求,底层使用的还是 HttpUrlconnection 。
- 更详细 OkHttp 解释请参考 扫盲系列 - Retrofit 基本用法
REST
既然 Retrofit 是一个 RESTful 的 HTTP 网络请求的框架的封装,那么什么是 RESTful 架构呢?
REST: REpresentational State Transfer的意思 ,是一组架构约束条件和原则
RESTful 都满足以下条件
- 每一个 URL 代表一个资源
- 客户端和服务器之间,传递这种资源的某种表现层
- 客户端通过四个 HTTP 动词,对服务端资源进行操作,实现表层状态转换
更多关于 REST 的介绍:什么是REST - GitHub讲解的非常详细
优缺点
优点
- 可以配置不同 HTTP client 来实现网络请求,如OkHttp、Httpclient等
- 请求的方法参数注解都可以定制
- 支持同步、异步和RxJava
- 超级解耦
- 可以配置不同的反序列化工具来解析数据,如json、xml等
- 使用非常方便灵活
- 框架使用了很多设计模式(感兴趣的可以看看源码学习学习)
缺点:
- 不能接触序列化实体和响应数据
- 执行的机制太严格
- 使用转换器比较低效
- 只能支持简单自定义参数类型
请求网络的流程
几种网络框架的对比
使用介绍:
大致分为 7 步,这里面会有一个实例应用。以获取豆瓣 Top250 榜单
- 添加 Retrofit 依赖库
- 创建接收器返回的数据类
- 创建用于描述网络请求的接口
- 创建 Retrofit 实例
- 创建网络请求接口实例并配置网络请求参数
- 发送网络请求(异步/同步)
- 处理服务器返回的数据
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 使用
说明:
- 因为 retrofit Retrofit 是基于 OkHttp 的封装的,所以肯定需要 OkHttp 的库
- 因为网络请求通常用到解析 JSON ,这里适用 Gson 库解析,以及 Retrofit 中转换成 JavaBean 的转换器converter-gson,当然,还有其他的转换器可以使用。
- 因为我们经常会把 Retrofit 和 RxJava 配合使用,所以可能还会用到 RxJava 相应的库
- 当然,需要添加网络权限
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);
}
解释:
@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")
,这样就不需要设置基本路径了。Call<MovieObject>
Call 就是一种返回类型, MovieObject 是我们上一步定义的实体类- getTop250 这个是方法名,就不解释了
- 接下来看方法中的参数,
@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); }
注意:
- 必须加上 @FormUrlEncoded标签,否则会抛异常
- 参数标签用 @Field 或者 @Body 或者 @FieldMap
- 使用 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);
解释:
- Part 后面支持三种类型, RequestBody ,MultipartBody.Part,任意类型
- 除了 MultipartBody.Part 以外,其他类型必须带上表单字段,因为 MultipartBody.Part 中已经包含了表单字段的信息
- 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();
注意:
- 使用的 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'
自定义拦截器
- 需要实现 Interceptor 接口,复写intercept(Chain chain)方法,并返回 Response
- 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 - 如何从服务器下载文件 c07e8)
既已览卷至此,何不品评一二: