Retrofit2与RxJava用法解析

目录
  1. 1. 单独使用Retrofit2
    1. 1.1. 引入类库
    2. 1.2. 构造http接口类
    3. 1.3. Get请求
    4. 1.4. Post请求(key/value)
    5. 1.5. Post请求(body体)
    6. 1.6. 单个文件上传
    7. 1.7. 多文件上传
    8. 1.8. 文件下载
  2. 2. 开启OKHttp的日志拦截
  3. 3. Retrofit2与RxJava整合
    1. 3.1. 引入类库
    2. 3.2. 构造http接口类
    3. 3.3. Get请求
    4. 3.4. Post请求(key/value)
    5. 3.5. 多文件上传
    6. 3.6. 文件下载
  4. 4. 总结

Retrofit2是square公司出品的一个网络请求库,目前非常流行,特别适合于rest请求。网上也有不少介绍该库的文章,但别人的终究是别人的,还需要转化为自己的才行。正所谓“纸上得来终觉浅,绝知此事要躬行”,本着学习的态度笔者对retroift2的用法进行了下列研究,主要包括以下几个方面

  • get请求
  • post请求(包括key/value,以及body)
  • 文件上传(进度监听)
  • 文件下载
  • 与RxJava整合

    单独使用Retrofit2

引入类库

1
2
3
compile 'com.squareup.retrofit2:retrofit:2.0.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'

构造http接口类

Retrofit2可以根据一个服务接口类,利用jdk动态代理生成它的相应实现。

1
2
3
4
5
retrofit=new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
myService=retrofit.create(MyService.class);

生成了代理类之后,就可以进行相应请求了

Get请求

1
2
@GET("rest/findUserForGet")
Call<User> findUserForGet(@Query("id") int id, @Query("username") String username,@Query("address") String address);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Call<User> userCall=myService.findUserForGet(12,"张明明","北京海淀区");
userCall.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
//主线程
Log.e(TAG,Thread.currentThread().getName());
Log.e(TAG,response.body().toString());

}

@Override
public void onFailure(Call<User> call, Throwable t) {
Log.e(TAG,t.getMessage());
}
});

需要注意Query注解不能丢,即使形参和请求的key相同也要加上,否则报错;另外回调函数发生在主线程,可以进行UI相关的操作

Post请求(key/value)

1
2
3
@FormUrlEncoded
@POST("rest/findUserForPost")
Call<ResponseBody> findUserForPost(@Field("id") int id, @Field("username") String username,@Field("address") String address);
1
2
3
4
5
6
7
8
9
10
11
12
13
Call<ResponseBody> userCall=myService.findUserForPost(9,"陈玄功","恶人谷");
userCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e(TAG,getResponsString(response.body()));

}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG,t.getMessage());
}
});

Post请求(body体)

1
2
@POST("rest/postBodyJson")
Call<User> postBodyJson(@Body User user);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
User user=new User();
user.setId(2);
user.setUsername("李明");
user.setBirthday("1995-09-06 09-09-08");
user.setSex("1");
Call<User> userCall=myService.postBodyJson(user);
userCall.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
Log.e(TAG,response.body().toString());
}

@Override
public void onFailure(Call<User> call, Throwable t) {
Log.e(TAG,t.getMessage());
}
});

此种方式会默认加上Content-Type: application/json; charset=UTF-8的请求头,即以JSON格式请求,再以JSON格式响应。

单个文件上传

1
2
3
4
@Multipart
@POST("rest/upload")
Call<ResponseBody> upload(@Part("username") RequestBody username,@Part("address") RequestBody address,
@Part MultipartBody.Part file);
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
File file=new File(Environment.getExternalStorageDirectory(),"测试01.jpg");

//普通key/value
RequestBody username =
RequestBody.create(
MediaType.parse("multipart/form-data"), "jim");

RequestBody address =
RequestBody.create(
MediaType.parse("multipart/form-data"), "天津市");

//file
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);


//包装RequestBody,在其内部实现上传进度监听
CountingRequestBody countingRequestBody=new CountingRequestBody(requestFile, new CountingRequestBody.Listener() {
@Override
public void onRequestProgress(long bytesWritten, long contentLength) {
Log.e(TAG,contentLength+":"+bytesWritten);
}
});


MultipartBody.Part body =
MultipartBody.Part.createFormData("file", file.getName(), countingRequestBody);


Call<ResponseBody> userCall=myService.upload(username, address, body);
userCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e(TAG, getResponsString(response.body()));

}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG, t.getMessage());
}
});

多文件上传

1
2
3
@Multipart
@POST("rest/upload")
Call<ResponseBody> uploads(@PartMap Map<String, RequestBody> params);
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
//必须使用LinkedHashMap,保证文件按顺序上传
Map<String,RequestBody> params=new LinkedHashMap<>();
File file1=new File(Environment.getExternalStorageDirectory(),"测试01.jpg");
RequestBody filebody1 =RequestBody.create(MediaType.parse("multipart/form-data"), file1);
//记录文件上传进度
CountingRequestBody countingRequestBody1=new CountingRequestBody(filebody1, new CountingRequestBody.Listener() {
@Override
public void onRequestProgress(long bytesWritten, long contentLength) {
Log.e(TAG,"file1:"+contentLength+":"+bytesWritten);
}
});
//file代表服务器接收到的key,file1.getName()代表文件名
params.put("file\";filename=\""+file1.getName(),countingRequestBody1);


File file2=new File(Environment.getExternalStorageDirectory(),"girl.jpg");
RequestBody filebody2 =RequestBody.create(MediaType.parse("multipart/form-data"), file2);
CountingRequestBody countingRequestBody2=new CountingRequestBody(filebody2, new CountingRequestBody.Listener() {
@Override
public void onRequestProgress(long bytesWritten, long contentLength) {
Log.e(TAG,"file2:"+contentLength+":"+bytesWritten);
}
});
params.put("file\";filename=\""+file2.getName(),countingRequestBody2);


File file3=new File(Environment.getExternalStorageDirectory(),"测试02.jpg");
RequestBody filebody3 =RequestBody.create(MediaType.parse("multipart/form-data"), file3);
CountingRequestBody countingRequestBody3=new CountingRequestBody(filebody3, new CountingRequestBody.Listener() {
@Override
public void onRequestProgress(long bytesWritten, long contentLength) {
Log.e(TAG,"file3:"+contentLength+":"+bytesWritten);
}
});
params.put("file\";filename=\""+file3.getName(),countingRequestBody3);

//普通key/value
params.put("username", RequestBody.create(
MediaType.parse("multipart/form-data"), "jim"));
params.put("address", RequestBody.create(
MediaType.parse("multipart/form-data"), "天津市"));

Call<ResponseBody> userCall=myService.uploads(params);
userCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e(TAG, getResponsString(response.body()));

}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG, t.getMessage());
}
});

文件下载

1
2
3
@Streaming
@GET("image/{filename}")
Call<ResponseBody> downFile(@Path("filename") String fileName);
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
Call<ResponseBody> userCall=myService.downFile(fname);
userCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {

String fileName=Environment.getExternalStorageDirectory()+"/"+fname;
FileOutputStream fos=new FileOutputStream(fileName);
InputStream is=response.body().byteStream();

byte[] buf=new byte[1024];
int len;
while ((len=is.read(buf))!=-1){
fos.write(buf,0,len);
}
is.close();
fos.close();

}catch (Exception ex){
Log.e(TAG,ex.getMessage());
}
Log.e(TAG,"success");

}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(TAG,t.getMessage());
}
});

开启OKHttp的日志拦截

Retrofit2底层还是使用的OKHttp,可以使用其相关的一些特性,比如开启日志拦截,此时就不能使用Retrofit2默认的OKHttp实例,需要自己单独构造,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

httpClient.addInterceptor(logging);

retrofit=new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();

开启日志后,会记录request和response的相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: --> POST http://192.168.1.104:8080/mobile/rest/postBodyJson http/1.1
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: Content-Type: application/json; charset=UTF-8
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: Content-Length: 71
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: {"birthday":"1995-09-06 09-09-08","id":2,"sex":"1","username":"李明"}
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: --> END POST (71-byte body)
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: <-- 200 OK http://192.168.1.104:8080/mobile/rest/postBodyJson (79ms)
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Server: Apache-Coyote/1.1
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Access-Control-Allow-Origin: *
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Content-Type: application/json;charset=UTF-8
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Transfer-Encoding: chunked
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Date: Sat, 14 May 2016 07:00:42 GMT
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: OkHttp-Sent-Millis: 1463209242134
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: OkHttp-Received-Millis: 1463209242206
05-14 03:00:42.208 5212-25519/com.bryan D/OkHttp: {"id":2,"username":"李明","sex":"1","birthday":"1995-09-06","address":null}
05-14 03:00:42.208 5212-25519/com.bryan D/OkHttp: <-- END HTTP (77-byte body)

Retrofit2与RxJava整合

引入类库

1
2
3
4
5
compile 'io.reactivex:rxandroid:1.1.0'
compile 'com.squareup.retrofit2:retrofit:2.0.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'

构造http接口类

Retrofit2可以根据一个服务接口类,利用jdk动态代理生成它的相应实现。

1
2
3
4
5
6
7
retrofit=new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(httpClient.build())
.build();
myService=retrofit.create(MyRxService.class);

Get请求

1
2
@GET("rest/findUserForGet")
Observable<User> findUserForGet(@Query("id") int id, @Query("username") String username,@Query("address") String address);
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
myService.findUserForGet(12,"张明明","北京海淀区")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<User>() {

@Override
public void onStart() {
super.onStart();
Log.e(TAG,"onStart");
}

@Override
public void onCompleted() {
Log.e(TAG,"onCompleted");
}

@Override
public void onError(Throwable e) {
Log.e(TAG,e.getMessage());
}

@Override
public void onNext(User user) {
Log.e(TAG,user.toString());
}
});

Post请求(key/value)

1
2
3
@FormUrlEncoded
@POST("rest/findUserForPost")
Observable<ResponseBody> findUserForPost(@Field("id") int id, @Field("username") String username, @Field("address") String address);

实现和Get类似

多文件上传

1
2
3
@Multipart
@POST("rest/upload")
Observable<ResponseBody> uploads(@PartMap Map<String, RequestBody> params);
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
//params构造部分和单独使用Retrofit2的构造相同
myService.uploads(params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<ResponseBody>() {

@Override
public void onStart() {
super.onStart();
Log.e(TAG, "onStart");
}


@Override
public void onCompleted() {
Log.e(TAG, "onCompleted");
}

@Override
public void onError(Throwable e) {
Log.e(TAG, e.getMessage());
}

@Override
public void onNext(ResponseBody body) {
Log.e(TAG, getResponsString(body));
}
});

文件下载

1
2
3
@Streaming
@GET("image/{filename}")
Observable<ResponseBody> downFile(@Path("filename") String fileName);

总结

无论是单独使用Retrofit2还是整合RxJava一起使用,请求体的构造部分并没有多大变化,主要区别是RxJava支持链式写法,可以对response作更为复杂的处理。现实业务中大多数也是两者结合起来使用。

Github Demo