Retrofit2
学习使用Retrofit和RxJava,简单记录一下重要的api和使用方法
需啊添加的依赖如下:1
2
3
4
5
6
7
8// Retrofit主要类库
implementation 'com.squareup.retrofit2:retrofit:2.0.2'
// Gson
implementation 'com.squareup.retrofit2:converter-gson:2.0.2'
// okHttp
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
// 日志拦截器
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
Retrofit主要使用注解,标记请求头,请求方式,请求参数,请求格式。主要记一下请求参数和请求格式的注解意义:
注解 | 使用 |
---|---|
@Body | 多用于post请求发送非表单数据,比如想要以post方式传递json格式数据 |
@Filed | 多用于post请求中表单字段,Filed和FieldMap需要FormUrlEncoded结合使用 |
@FiledMap | 和@Filed作用一致,用于不确定表单参数 |
@Part | 用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况 |
@PartMap | 用于表单字段,默认接受的类型是Map<String,REquestBody>,可用于实现多文件上传 |
@Path | 用于url中的占位符 |
@Query | 用于Get中指定参数 |
@QueryMap | 和Query使用类似 |
@Url | 指定请求路径 |
注解 | 使用 |
---|---|
@FormUrlEncoded | 表示请求发送编码表单数据,每个键值对需要使用@Field注解 |
@Multipart | 表示请求发送multipart数据,需要配合使用@Part |
@Streaming | 表示响应用字节流的形式返回.如果没使用该注解,默认会把数据全部载入到内存中.该注解在在下载大文件的特别有用 |
使用示例: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
70public interface ApiService {
/*
设置Url的三种方式
*/
Call<Entity> post1( String url, Map<String, String> map);
Call<Entity> post2( Map<String, String> map);
Call<Entity> post3( String url, Map<String, String> map);
/**
* @param getId Query请求参数设置,将会以"?id=getId"
*/
Call<Entity> get(int getId) String token, ;
/**
* 使用@Body注解实现传入实体,自行转化成Json
*/
Call<Entity> login( String url, Entity post);
/**
* 上传图片
* retrofit 2.0的上传和以前略有不同,使用@Multipart注解和@Part MultipartBody实现。
*/
Call<Entity> upload( String url, MultipartBody.Part file);
/**
* 多张图片上传
*/
Call<Entity> upload( Map<String, MultipartBody.Part> map);
/**
* 上传图文
*/
Call<Entity> register( Entity post, Map<String, MultipartBody.Part> map);
/**
* 文件下载
*/
Call<Entity> downloadPicture( String fileUrl);
/**
* 若要下载大体积的文件,如10m以上,强烈建议使用@Streaming进行注解,否则将会出现IO异常.
*/
Observable<Entity> downloadPicture2( String fileUrl);
Observable<Entity> executePost( Map<String, Object> maps);
}
注:使用@Path出现url被转义问题
设置Url的方法中,如果这样使用,就会出现url被转义,即‘/’被转义为乱码1
2
Call<Entity> post( String url);
因此使用这种方式需要这样写1
2
Call<Entity> post( String url)
使用与配置
说了这么多,终于开始具体使用。
简单使用
首先需要创建一个请求方法接口ApiService,在其中编写需要用到的请求方法接口;
在创建一个RetrofitService类用于初始化及启动retrofit。代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class MyRetrofitService {
private MyRetrofitService(){}
static OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10000L, TimeUnit.MILLISECONDS)
.readTimeout(10000L, TimeUnit.MILLISECONDS)
.build();
public static Retrofit retrofit = new Retrofit.Builder()
.baseUrl("URL")
.client(okHttpClient)
// 设置json转换器
.addConverterFactory(GsonConverterFactory.create())
.build();
}
具体调用 时:1
2
3
4
5
6
7
8
9
10
11
12ApiService api = MyRetrofitService.retrofit.create(ApiService.class);
Call<ResponseBody> mData = api.get();
mData.enqueue(new Callback<ResponseBody>() {
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
关于OkHttpClient更多具体配置
拦截器
addNetworkInterceptor 用于添加网络拦截器Network Interfacetor,它会在request和response时分别被调用一次;
addInterceptor 用于添加应用拦截器Application Interceptor,只会在response被调用一次。
1. 日志拦截器
使用addNetworkInterceptor()添加到OkHttpClient中
日志拦截器有两种创建方式:
- 使用HttpLoggingInterceptor
1
2
3
4
5
6
7
8
9
10
11public static HttpLoggingInterceptor getHttpLoggingInterceptor() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(
new HttpLoggingInterceptor.Logger() {
public void log(String message) {
Log.e("OkHttp", "log : " + message);
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return loggingInterceptor;
} - 普通Interceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17private class LogInterceptor implements Interceptor {
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Log.d("OkHttp", "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("OkHttp", "HttpHelper2" + String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}2. 请求头拦截器
使用addInterceptor()添加到OkHttpClient中请求头拦截器为了让服务端能更好的识别该请求,服务器那边通过请求头判断该请求是否为有效请求等…
1 | /** |
3. 统一请求拦截器
使用addInterceptor()添加到OkHttpClient中
统一请求拦截器的功能跟请求头拦截器相类似1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* 拦截器Interceptors
* 统一的请求参数
*/
private Interceptor commonParamsInterceptor() {
Interceptor commonParams = new Interceptor() {
public okhttp3.Response intercept(Chain chain) throws IOException {
Request originRequest = chain.request();
Request request;
HttpUrl httpUrl = originRequest.url().newBuilder().
addQueryParameter("paltform", "android").
addQueryParameter("version", "1.0.0").build();
request = originRequest.newBuilder().url(httpUrl).build();
return chain.proceed(request);
}
};
return commonParams;
}
4. 时间拦截器
使用addInterceptor()方法添加到OkHttpClient中
从响应中获取服务器返回的时间1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* 通过响应拦截器实现了从响应中获取服务器返回的time
*/
public static Interceptor getResponseHeader() {
Interceptor interceptor = new Interceptor() {
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,然后在创建缓存拦截器
1
2
3
4
5
6
7// 创建Cache
File httpCacheDirectory = new File(MyApp.getContext().getCacheDir(), "OkHttpCache");
Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
httpClientBuilder.cache(cache);
// 设置缓存
httpClientBuilder.addNetworkInterceptor(getCacheInterceptor2());
httpClientBuilder.addInterceptor(getCacheInterceptor2()); - 缓存拦截器
缓存时间自己根据情况设定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/**
* 在无网络的情况下读取缓存,有网络的情况下根据缓存的过期时间重新请求
*/
public Interceptor getCacheInterceptor2() {
Interceptor commonParams = new Interceptor() {
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", "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 commonParams;
}自定义CookieJar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15httpClientBuilder.cookieJar(new CookieJar() {
final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookieStore.put(url, cookies);//保存cookie
//也可以使用SP保存
}
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url);//取出cookie
return cookies != null ? cookies : new ArrayList<Cookie>();
}
});使用RxJava
使用RxJava的目的是,RxJava可以更加简洁的实现异步操作。RxJava的简单理解
Rxjava使用观察者模式实现,因此其中有几个基本概念就是Observable被观察者,Observer观察者,subscribe订阅事件。(关于观察者模式最经典的实现就是OnClickListener)Observable与Observer通过subcribe()实现订阅关系,Observable就可以在发生指定事件时通知Observer。
不同于传统的观察者模式,Rxjava的回调方法除了onNext()(相当于onClick()),还有onCompleted()和onError()方法。由于Rxjava会将每个事件作为一个事件队列处理,并把每个事件单独处理。以此为前提既可以理解另外两个方法: - onCompleted() 当事件队列结束,不会有新的onNext()发出时,即会触发onCompleted()方法。
- onError() 当事件队列出现异常是会触发onError(),并且会同时停止事件队列,不允许新的事件发出。
- 那么可能会有疑问,异常以后没有事件发出,是否会调用onCompleted()。答案是不会,在一个正常的时间序列中,onCompleted()与onError()方法有且只有一个,二者互斥,即调用其中一个就不会调用另外一个,且都是事件队列中最后一个调用。
Rxjava的简单实现
注:一下代码的实现依据rxjava版本为,io.reactivex.rxjava:1.3.8,是一个比较旧的版本了,从这以后rxjava开始2.0版本,有了一些新的改动。但是以此版本理解RxJava的使用思想还是没有问题,而且更加简易更好理解。
创建观察者Observer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Observer<String> observer = new Observer<String>() {
public void onNext(String s) {
Log.d(tag, "Item: " + s);
}
public void onCompleted() {
Log.d(tag, "Completed!");
}
public void onError(Throwable e) {
Log.d(tag, "Error!");
}
};除了Observer接口,RxJava还内置了一个实现了Observer的抽象类:Subscriber。 Subscriber是Observer的扩展,但他们的基本使用方式完全一样(同上)。
实质上,在RxJava的subscribe过程中,Observer也总是会先被转换成一个Subscriber再使用。关于区别
onStart(): 这是 Subscriber 增加的方法。它会在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认情况下它的实现为空。需要注意的是,如果对准备工作的线程有要求(例如弹出一个显示进度的对话框,这必须在主线程执行), onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用 doOnSubscribe() 方法,具体可以在后面的文中看到。
unsubscribe(): 这是 Subscriber 所实现的另一个接口 Subscription 的方法,用于取消订阅。在这个方法被调用后,Subscriber 将不再接收事件。一般在这个方法调用前,可以使用 isUnsubscribed() 先判断一下状态。 unsubscribe() 这个方法很重要,因为在 subscribe() 之后, Observable 会持有 Subscriber 的引用,这个引用如果不能及时被释放,将有内存泄露的风险。所以最好保持一个原则:要在不再使用的时候尽快在合适的地方(例如 onPause() onStop() 等方法中)调用 unsubscribe()来解除引用关系,以避免内存泄露的发生。创建Observable被观察者
1
2
3
4
5
6
7
8
9Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("Hi");
subscriber.onNext("Aloha");
subscriber.onCompleted();
}
});create()方法中传入了参数OnSubscribe对象会被存储在Observable对象中,相当于一个计划表。当Observable被订阅,将回调call()方法,依次触发call中的事件序列。因此例如实际使用中,就可以在call()中进行异步操作,如网络请求,然后将请求的结果通过onNext()的参数发送出去。
除了使用create()方法,Rxjava还提供其他方法创建事件队列- just(T…)
1
2// 作用等同上例
Observable observable = Observable.just("Hello", "Hi", "Aloha"); - from(T[]) / from(Iterable<? extends T>)
1
2
3// 将传入的数组或可迭代对象拆分成具体对象后,依次发送出来。
String[] words = {"Hello", "Hi", "Aloha"};
Observable observable = Observable.from(words);
- just(T…)
- 实现订阅Subscribe
1
observable.subscribe(observer);
这看起来是『observalbe 订阅了 observer / subscriber』,思维逻辑上虽然有些别扭,但是这有利于流式api的设计
Observable.subscribe()内部代码结构如下1
2
3
4
5public Subscription subscribe(Subscriber subscriber) {
subscriber.onStart();
onSubscribe.call(subscriber);
return subscriber;
}将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe().
进阶用法
subscriber()支持不完整定义的回调,可以根据定义自动创建Subscriber。使用方式如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Action1<String> onNextAction = new Action1<String>() {
public void call(String s) {
System.out.println(s );
}
};
// Lambda表达式写法,内容同上
Action1<Throwable> onErrorAction = (e) -> System.out.println("error" + e);
Action0 onComAction = () -> System.out.println("com");
Observable observable = Observable.just("Hello", "Aloha");
// 自动创建Subscriber
observable.subscribe(onNextAction);
observable.subscribe(onNextAction, onErrorAction);
observable.subscribe(onNextAction, onErrorAction, onComAction);
简单解释一下这段代码中出现的 Action1 和 Action0。
Action0 是 RxJava 的一个接口,它只有一个方法 call(),这个方法是无参无返回值的;由于 onCompleted() 方法也是无参无返回值的,因此 Action0 可以被当成一个包装对象,将 onCompleted() 的内容打包起来将自己作为一个参数(onComAction)传入 subscribe() 以实现不完整定义的回调。这样其实也可以看做将 onCompleted() 方法作为参数传进了 subscribe(),相当于其他某些语言中的『闭包』。
Action1 也是一个接口,它同样只有一个方法 call(T param),这个方法也无返回值,但有一个参数;与 Action0 同理,由于 onNext(T obj) 和 onError(Throwable error) 也是单参数无返回值的,因此 Action1 可以将 onNext(obj) 和 onError(error) 打包起来传入 subscribe() 以实现不完整定义的回调。事实上,虽然 Action0 和 Action1 在 API 中使用最广泛,但 RxJava 是提供了多个 ActionX 形式的接口 (例如 Action2, Action3) 的,它们可以被用以包装不同的无返回值的方法。
简单来说就是一个有参一个无参的包装类,无参的类可以用于构建onCompleted(),有参的可以用于构建onNext()或onError()。subsrcribe()重载了3个关于ActionX的方法,就是上述代码中的3个。比如第三个方法参数表为(Action1<? super T>, Action1
接下来用一个例子小结一下:
由 id 取得图片并显示: 由指定的一个 drawable 文件 id drawableRes 取得图片,并显示在 ImageView 中,并在出现异常的时候打印 Toast 报错:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
public void call(Subscriber<? super Drawable> subscriber) {
Drawable drawable = getTheme().getDrawable(drawableRes));
subscriber.onNext(drawable);
subscriber.onCompleted();
}
}).subscribe(new Observer<Drawable>() {
public void onNext(Drawable drawable) {
imageView.setImageDrawable(drawable);
}
public void onCompleted() {
}
public void onError(Throwable e) {
Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
}
});
以上代码,对上面RxJava的内容进行了小结。可以看到,在Observable的call()方法中进行所要进行的异步操作,然后在subcribe的Observer(Subscriber)中通过onNext,onError,onCompleted设置回调方法。这就是Rxjava的基础使用格式。
异步-线程控制Scheduler
然而到这里,都和开头所说RxJava的主要作用异步还没有什么关系。因为Rxjava默认是在运行在同一线程。
想要实现异步机制,就需要用到RxJava的第四个概念Scheduler。
观察者模式本身的目的就是『后台处理,前台回调』的异步机制
在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler (调度器)。
RxJava通过Scheduler指定一个代码段需要运行在哪一个进程。
- subscribeOn() 指定subscribe()发生的进程。即 Observable.OnSubscribe 被激活时所处的线程,事件产生的线程。
- observeOn() 指定Subscriber所运行的线程。即事件消费进程。
RxJava中内置了几个进程,以适用一些默认场景:
- Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的 Scheduler。
- Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
- Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
- Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
- Android 还有一个专用的 AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行。
还是使用Android中图片显示的例子
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 int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
public void call(Subscriber<? super Drawable> subscriber) {
Drawable drawable = getTheme().getDrawable(drawableRes));
subscriber.onNext(drawable);
subscriber.onCompleted();
}
})
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Observer<Drawable>() {
public void onNext(Drawable drawable) {
imageView.setImageDrawable(drawable);
}
public void onCompleted() {
}
public void onError(Throwable e) {
Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
}
});即 加载图片将会发生在 IO 线程,而设置图片则被设定在了主线程。这就意味着,即使加载图片耗费了几十甚至几百毫秒的时间,也不会造成丝毫界面的卡顿。
Schedule的原理:变换
先具体实现一下RxJava+Retrofit
未完待续…