原文地址 segmentfault.com
Retrofit 简介
Retrofit 是一个在 Android 开发中非常流行的网络框架,底层依赖 OkHttp。
Retrofit 和 OkHttp 都出自 Square 的技术团队。
Retrofit 的 GitHub 地址
应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装
==Header、URL、请求参数==等信息,之后由 OkHttp
完成后续的请求操作,在服务端返回数据之后, OkHttp 将原始的结果交给
Retrofit, Retrofit 再根据用户的需求==对结果进行解析==的过程。
Retrofit 支持大多数的 Http 方法。
Retrofit 的特点如下:
- Retrofit 是可插拔的,允许不同的执行机制及其库用于执行 http
调用。允许 API
请求,与应用程序其余部分中任何现有线程模型或任务框架无缝组合。
Retrofit 为常见的框架提供了适配器 (Adapter):
- RxJaval.x Observable & Single -
com.squareup.retrofit2:adapter-rxjava
- RxJava2.x Observable, Flowable, Single, Completable & Maybe -
com.squareup.retrofit2:adapter-rxjava2
- Guava ListenableFuture - com.squareup.retrofit2:adapter-guava
- Java 8 CompletableFuture - com.squareup.retrofit2:adapter-java8
- )允许不同的序列化格式及其库,用于将 Java 类型转换为其 http
表示形式,并将 http 实体解析为 Java 类型。
Retrofit 为常见的序列化格式提供了转换器 (Converter):
- Gson: com.squareup.retrofit2:converter-gson
- Jackson: com.squareup.retrofit2:converter-jackson
- Moshi: com.squareup.retrofit2:converter-moshi
- Protobuf: com.squareup.retrofit2:converter-protobuf
- Wire: com.squareup.retrofit2:converter-wire
- Simple XML: com.squareup.retrofit2:converter-simplexml
- Scalars (primitives, boxed, and String):
com.squareup.retrofit2:converter-scalars
开源社区也己经为其他库和序列化格式创建了各种第三方转换器
(Converter):
- LoganSquare -
com.github.aurae.retrofit2:converter-logansquare:1.4.1
- FastJson - org.ligboy.retrofit2:converter-fastjson:2.1.0 和
org.ligboy.retrofit2:converter-fastjson-android:2.1.0
OkHttp 的特点如下:
- 支持 HTTP2/SPDY 黑科技
- socket 自动选择最优路线,并支持自动重连。
- 拥有自动维护的 socket 连接池,减少握手次数
- 拥有队列线程池,轻松写并发
- 拥有 Interceptors 轻松处理请求与响应(比如透明 GZIP 压缩、
LOGGING)
- 基于 Headers 的缓存策略。
Retrofit 与 RxJava
的完美配合
Retrofit 是一个网络框架,如果想尝试响应式的编程方式,则可以结合
RxJava 一起使用。Retrofit 对 RxJava1 和 RxJava2 都提供了 Adapter。
案例:将苏州市南门地区的 PM2.5、PM10、SO2 的数据展示到 App 上。
pm25.in
是一个公益性的网站,免费提供空气质量数据。在调用这些接口之前,
需要去该网站注册,并申请一个 AppKey
Retrofit 使用步骤如下:
1. 添加 Retrofit 依赖
在 App 的 build.gradle 中添加所需要的 Retrofit 库,以及 RxJava2 的
adapter 库。
1 2 3 4
| implementation 'com.squareup.retrofit2:retrofit:2.7.1' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1' implementation 'org.ligboy.retrofit2:converter-fastjson-android:2.1.0' implementation "com.squareup.okhttp3:logging-interceptor:4.3.1"
|
2. 创建 RetrofitManager
一般需要创建 Retrofit 管理类,在这里创建一个名为 RetrofitManager
类,方便在
整个 App 中使用。
RetrofitManager 代码如下:
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
| public class RetrofitManager {
private static Retrofit retrofit;
public static Retrofit retrofit() { if (retrofit == null) { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BASIC);
OkHttpClient okHttpClient = new OkHttpClient.Builder() .writeTimeout(30_1000, TimeUnit.MILLISECONDS) .readTimeout(20_1000, TimeUnit.MILLISECONDS) .connectTimeout(15_1000, TimeUnit.MILLISECONDS) .addInterceptor(loggingInterceptor) .build();
retrofit = new Retrofit.Builder() .baseUrl(APIService.API_BASE_SERVER_URL) .addConverterFactory(FastJsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(okHttpClient) .build(); }
return retrofit; } }
|
3. 创建 APIService
接下来,我们需要定义网络请求的接口。 pm25.in
提供了多个获取空气质量数据的接口,这里选取其中 3
个接口,分别是获取一个城市所有监测点的 PM2.5
数据、获取一个城市所有监测点的 PM10 数据、获取一个城市所有监测点的 SO2
数据接口。
1 2 3 4 5 6 7 8 9 10 11 12
| public interface APIService { String API_BASE_SERVER_URL = "http://www.pm25.in/";
@GET("api/querys/pm2_5.json") Maybe<List<PM25Model>> pm25(@Query("city") String city, @Query("token") String token);
@GET("api/querys/pm10.json") Maybe<List<PM10Model>> pm10(@Query("city") String city, @Query("token") String token);
@GET("api/querys/so2.json") Maybe<List<SO2Model>> so2(@Query("city") String city, @Query("token") String token); }
|
在 APIService 中,每个方法返回的类型都是 Maybe 类型,其实也可以返回
Observable、Flowable 等类型。
4. Retrofit 的使用
下面的代码分别调用了 3 个接口,井过滤出了南门地区的相关数据。
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
| APIService apiService = RetrofitManager.retrofit().create(APIService.class);
apiService.pm25(Constant.CITY, Constant.TOKEN) .compose(RxJavaUtils.<List<PM25Model>>maybeToMain()) .filter(new Predicate<List<PM25Model>>() { @Override public boolean test(List<PM25Model> pm25Models) throws Exception { return pm25Models != null && !pm25Models.isEmpty(); } }) .flatMap(new Function<List<PM25Model>, MaybeSource<PM25Model>>() { @Override public MaybeSource<PM25Model> apply(List<PM25Model> pm25Models) throws Exception { for (PM25Model pm25Model : pm25Models) { if ("南门".equals(pm25Model.position_name)) { return Maybe.just(pm25Model); } } return null; } }) .subscribe(new Consumer<PM25Model>() { @Override public void accept(PM25Model pm25Model) throws Exception { Log.d(TAG, "PM25.Success-> " + pm25Model); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { Log.d(TAG, "PM25.Error-> " + throwable); } });
apiService.pm10(Constant.CITY, Constant.TOKEN) .compose(RxJavaUtils.<List<PM10Model>>maybeToMain()) .filter(new Predicate<List<PM10Model>>() { @Override public boolean test(List<PM10Model> pm10Models) throws Exception { return pm10Models != null && !pm10Models.isEmpty(); } }) .flatMap(new Function<List<PM10Model>, MaybeSource<PM10Model>>() { @Override public MaybeSource<PM10Model> apply(List<PM10Model> pm10Models) throws Exception { for (PM10Model pm10Model : pm10Models) { if ("南门".equals(pm10Model.position_name)) { return Maybe.just(pm10Model); } } return null; } }) .subscribe(new Consumer<PM10Model>() { @Override public void accept(PM10Model pm10Model) throws Exception { Log.d(TAG, "PM10.Success-> " + pm10Model); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { Log.d(TAG, "PM10.Error-> " + throwable); } });
apiService.so2(Constant.CITY, Constant.TOKEN) .compose(RxJavaUtils.<List<SO2Model>>maybeToMain()) .filter(new Predicate<List<SO2Model>>() { @Override public boolean test(List<SO2Model> so2Models) throws Exception { return so2Models != null && !so2Models.isEmpty(); } }) .flatMap(new Function<List<SO2Model>, MaybeSource<SO2Model>>() { @Override public MaybeSource<SO2Model> apply(List<SO2Model> so2Models) throws Exception { for (SO2Model so2Model : so2Models) { if ("南门".equals(so2Model.position_name)) { return Maybe.just(so2Model); } } return null; } }) .subscribe(new Consumer<SO2Model>() { @Override public void accept(SO2Model so2Model) throws Exception { Log.d(TAG, "SO2.Error-> " + so2Model); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { Log.d(TAG, "SO2.Error-> " + throwable); } });
|
这里还使用了 maybeToMain() 方法,它的代码如下:
1 2 3 4 5 6 7
| @JvmStatic fun <T> maybeToMain(): MaybeTransformer<T, T> { return MaybeTransformer { upstream -> upstream.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) } }
|
它用于切换线程,返回 MaybeTransformer 对象。因为 apiService
中每个返回的方法都是
Maybe 类型,所以这里会用到 MaybeTransformer 。使用了 maybeToMain() 后
,除网络请求是在
io() 线程中运行外,其余的操作都是在主线程中运行的.
也可以让 filter、 flatMap 操作也在 io()
线程中运行,展示数据时才切换回主线程。
5. 常见使用场景
接下来列举一些 Retrofit 其余常见的使用场景。
- 合并多个网络请求
如:需要在某一个信息流列表中插入多条广告,每一条广告都需要做一次网络请求。这时就可以考虑使用
zip
操作符,将请求信息流,以及请求的多个广告的请求合并起来,等所有请求完成之后,再用合并函数将广告插到信息流固定的位置上,最后以列表的形式呈现给用户。
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
| APIService apiService = RetrofitManager.retrofit().create(APIService.class);
Maybe<PM25Model> pm25ModelMaybe = apiService.pm25(Constant.CITY, Constant.TOKEN) .compose(RxJavaUtils.<List<PM25Model>>maybeToMain()) .filter(new Predicate<List<PM25Model>>() { @Override public boolean test(List<PM25Model> pm25Models) throws Exception { return pm25Models != null && !pm25Models.isEmpty(); } }) .flatMap(new Function<List<PM25Model>, MaybeSource<PM25Model>>() { @Override public MaybeSource<PM25Model> apply(List<PM25Model> pm25Models) throws Exception { for (PM25Model pm25Model : pm25Models) { if ("南门".equals(pm25Model.position_name)) { return Maybe.just(pm25Model); } } return null; } });
Maybe<PM10Model> pm10ModelMaybe = apiService.pm10(Constant.CITY, Constant.TOKEN) .compose(RxJavaUtils.<List<PM10Model>>maybeToMain()) .filter(new Predicate<List<PM10Model>>() { @Override public boolean test(List<PM10Model> pm10Models) throws Exception { return pm10Models != null && !pm10Models.isEmpty(); } }) .flatMap(new Function<List<PM10Model>, MaybeSource<PM10Model>>() { @Override public MaybeSource<PM10Model> apply(List<PM10Model> pm10Models) throws Exception { for (PM10Model pm10Model : pm10Models) { if ("南门".equals(pm10Model.position_name)) { return Maybe.just(pm10Model); } } return null; } });
Maybe<SO2Model> so2ModelMaybe = apiService.so2(Constant.CITY, Constant.TOKEN) .compose(RxJavaUtils.<List<SO2Model>>maybeToMain()) .filter(new Predicate<List<SO2Model>>() { @Override public boolean test(List<SO2Model> so2Models) throws Exception { return so2Models != null && !so2Models.isEmpty(); } }) .flatMap(new Function<List<SO2Model>, MaybeSource<SO2Model>>() { @Override public MaybeSource<SO2Model> apply(List<SO2Model> so2Models) throws Exception { for (SO2Model so2Model : so2Models) { if ("南门".equals(so2Model.position_name)) { return Maybe.just(so2Model); } } return null; } });
Maybe.zip(pm25ModelMaybe, pm10ModelMaybe, so2ModelMaybe, new Function3<PM25Model, PM10Model, SO2Model, ZipObject>() { @Override public ZipObject apply(PM25Model pm25Model, PM10Model pm10Model, SO2Model so2Model) throws Exception { Log.d(TAG, "zip-> \r\n" + pm25Model + "\r\n" + pm10Model + "\r\n" + so2Model); ZipObject zipObject = new ZipObject();
zipObject.pm2_5 = pm25Model.pm2_5; zipObject.pm2_5_24h = pm25Model.pm2_5_24h; zipObject.pm2_5_quality = pm25Model.quality;
zipObject.pm10 = pm10Model.pm10; zipObject.pm10_24h = pm10Model.pm10_24h;
zipObject.so2 = so2Model.so2; zipObject.so2_24h = so2Model.so2_24h;
return zipObject; } }).subscribe(new Consumer<ZipObject>() { @Override public void accept(ZipObject zipObject) throws Exception { Log.d(TAG, "Success-> " + zipObject); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { Log.d(TAG, "Error-> " + throwable); } });
|
- 返回默认值
有时, 网络请求失败可以使用 onErrorReturn 操作符,
一个空的对象作为默认值。
- 多个网络请求嵌套使用
若是 A 请求完成之后,才能去调用 B 请求,则可以考虑使用 flatMap
操作符。
- 重试机制
对于一些重要的接口,需要采用重试机制。因为有些时候用户的网络环境比较差,第一次请求接口超时了,那么再一次请求可能就会成功。虽然有一定的延时,但至少返回了数据,保证了用户体验。
1 2
| apiService.loadContent(params) .retryWhen(new RetryWithDelay(3, 1000));
|
在这里 retryWhen 操作符与 RetryWithDelay 一起搭配使用,表示有 3
次重试机会,每次的延迟时间是 1000ms。 RetryWithDelay 是一个工具类,使用
kotlin 语言编写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class RetryWithDelay( private val maxRetries: Int, private val retryDelayMillis: Int ) : Function<Flowable<out Throwable>, Publisher<*>> {
private var retryCount: Int = 0
init { this.retryCount = 0 }
override fun apply(attempts: Flowable<out Throwable>): Publisher<*> { return attempts.flatMap { throwable -> if (++retryCount <= maxRetries) { Flowable.timer(retryDelayMillis.toLong(), TimeUnit.MILLISECONDS) } else { Flowable.error(throwable) } } } }
|