Android 使用Retrofit2进行网络请求

Android 开发中,有多种网络请求框架,从一开始自己写”框架”,到后面使用第三方框架,接触下来个人最喜欢的是 Retfofit,然而 Retrofit 并不是网络请求框架,可以把它看作是对网络请求接口进行优化配置的一个框架,真正的网络请求处理是它所依赖的 OkHttp处理的,本篇主要记录使用 Retrofit2 + RxJava 框架来进行网络请求的一些方法。

Configure

配置 build.gradle

1
2
3
4
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

Interface Api

定义网络请求接口,不需要定义完整的url,在 Retrofit 中,需要把一个完整的 url 拆分成两部分,比如 http://10.50.40.53:8081/v1/users ,需要拆分成两部分:

  • baseUrl http://10.50.40.53:8081/,后面在 Retrofit 实例化的时候会使用到,
  • apiPath v1/users 则是请求的服务接口具体路径地址,定义接口时指定服务接口的请求方式,通常是 GETPOST ,来完成一个api接口的定义。
1
2
3
4
5
6
7
public interface WebService {
static final String BaseService = "v1/";
@GET(BaseService + "users")
Observable<User> users();
}

使用 @GET 注解修饰 api 接口 users ,表示 users 接口的请求方式为 GET@GET 注解中的地址则是该 api 请求的服务器接口地址。该接口的返回值是 Observable 对象,关于该对象可参考 RxJava

Retrofit init

使用 Retforit.Builder 生成一个 Retrofit 实例,通常在一个应用中Retrofit 实例为一个单例对象。

1
2
3
4
5
6
Retrofit retrofit2 = new Retrofit.Builder()
.baseUrl("http://10.50.40.53:8081/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
WebService service = retrofit2.create(WebService.class);
  • baseUrl 指定服务器地址,通常以 / 结束;
  • addConverterFactory 指定数据转换器,该转换器会将服务器返回的响应数据自动转换为接口中定义 Bean 数据类型,上面的 users 接口,在服务器响应之后,则会自动把服务器返回的数据转换为 User 对象,GsonConverterFactory 就是将服务器返回的数据使用 Gson 转换器转换为 Java 中的实体对象的一个工具类;
  • addCallAdapterFactory 在 users 接口中,定义的返回数据类型是 Observable ,上面的 addConverterFactory 只是把数据转换成了 Java 实体对象,如果想使用 RxJava 中的 Observable ,还需要使用 RxJava2CallAdapterFactoryUser 对象进行封装返回;

Request

在使用 retrofit 生成了 service 接口的实例之后,即可调用该接口进行网络请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
service.users()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(User value) {
Log.d(TAG, "onError: " + value);
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "onError: " + e);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
});

请求完成后,如果请求成功会执行 onNext 方法,失败则执行 onError,最后执行 onComplete 方法。

服务器返回的数据 {“name”: “Michael Cai”, “age”: 20} ,会被自动转换成一个 User 对象。

1
2
3
4
5
6
public class User {
private String name;
private int age;
// ...
}

常用的Annotation

GET 组合 PathQuery

1
2
3
4
5
6
7
8
9
10
11
12
// users/yyn
@GET("users/{user}")
Observable<Data<User>> ex_Path(@Path("user") String user);
// users?user=ccf
@GET("users")
Observable<Data<User>> ex_Query(@Query("user") String user);
// 根据 id 列表获取用户列表
// 后台获取到的参数为 userIds = 10001, userIds = 10002 ...
@GET("users")
Observable<User> login(@Query("userIds") List<String> userIds);

Query可以是单个字符串对象,也可以是一个集合。
Path 表示请求路径中 user 部分是动态替换的。

PathQuery还可以组合在一起使用:

1
2
3
4
5
6
7
8
9
10
11
// users/list?sort=desc
@GET("users/list?sort=desc")
Observable<Data<User>> ex_Query();
// users/ccf?sort=1
@GET("users/{name}")
Observable<Data<User>> ex_PathWithQuery(@Path("name") String name, @Query("sort") int sort);
// users/ccf?password=123456&username=admin
@GET("users/{name}")
Observable<Data<User>> ex_PathWithQueryMap(@Path("name") String name, @QueryMap Map<String, String> kvs);

POST 组合 FieldFieldMap

  • 使用 Field
1
2
3
4
5
// users/edit
// first_name=cai
@FormUrlEncoded
@POST("users/edit")
Observable<Data<User>> ex_Field(@Field("first_name") String first);
  • 使用 FieldMap
1
2
3
@FormUrlEncoded
@POST("users/edit")
Observable<User> login(@FieldMap HashMap<String, String> map);
  • 使用 Body
1
2
3
@POST(ServiceName + "login")
Observable<User> login(@Body User user);
// User对象中的所有属性名及对应的属性值将被当作参数及参数值传到服务器。

注: GET不能使用Body参数,Body表示的是传递的请求内容,在GET请求中是没有的。

HTTPBody

1
2
3
4
5
6
7
8
9
10
11
// users/new
// {"password":"123456", "username":"admin"}
@HTTP(method = "POST", path = "users/new", hasBody = true)
Observable<Data<User>> ex_Body_Object(@Body User ur);
// The object will also be converted using a converter specified on the Retrofit instance.
// If no converter is added, only RequestBody can be used.
// users/new
// {"password":"123456", "username":"admin"}
@HTTP(method = "POST", path = "users/new", hasBody = true)
Observable<Data<User>> ex_Body_Map(@Body Map<String, String> map);

HeaderHeaders

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Headers("Cache-Control: max-age=640000")
@GET("users/all")
Call<List<Widget>> ex_Headers();
@Headers({
"Content-Type: application/json;charset=UTF-8",
"User-Agent: Retrofit-Sample-App"
})
@GET("users/all")
Call<List<Widget>> ex_Headers2();
// 动态Header
@GET("users")
Call<User> ex_Header(@Header("Authorization") String authorization)
@GET("users")
Call<User> ex_HeaderMap(@HeaderMap Map<String, String> headers)

Headers 用于在请求中预设一些 Header。
Header 用于在请求方法中动态的传入 Header。

接口定义中的 Header ,在 Interceptor 里面,可以通过 request.header(“key”) 获取到所定义的 header 值;

Multipart

上传文件需要使用 @Multipart 注解,文件需要被包装到 Map 对象中。

1
2
3
4
5
6
7
8
// users/photo
@Multipart
@PUT("users/photo")
Call<User> ex_Part(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
@Multipart
@POST("file?__type=1")
Observable<Result> fileUpload(@PartMap Map<String, RequestBody> params);

将文件转换成 Map 对象,注意需要传入正确的 MediaType ,在下面的例子中使用的是 application/excel 表示传的文件是 excel 文件。

1
2
3
4
5
6
7
8
public Map<String, RequestBody> packBody(File file) {
Map<String, RequestBody> map = new HashMap<>();
RequestBody fileBody = RequestBody.create(MediaType.parse("application/excel"), file);
RequestBody userId = RequestBody.create(MediaType.parse("text/plain"), "10001");
map.put("userId", userId);
map.put("file\"; filename=\"" + file.getName() + "", fileBody);
return map;
}

上传多个文件

1
2
3
4
5
6
7
8
Map<String, RequestBody> params = new HashMap<>();
List<String> paths = ...;
for (String image : paths) {
File file = new File(image);
RequestBody images = RequestBody.create(MediaType.parse("image/*"), file);
params.put("images\"; filename=\"" + file.getName() + "", images);
}
service.updateImages(params);
1
2
3
@Multipart
@POST("/analyze")
Observable<String> upLoadImage(@Part("api_key") String api_key, @Part ("api_secret") String api_secret, @Part MultipartBody.Part file);

ChangeURL

之前的配置方式,所要访问的 baseUrlapiPath 都是固定的,除了固定的配置方法,还可以使用 @Url 动态配置。

动态配置 URL 需要注意的地方是 GET 请求需要注意 @Url 参数的位置,@Url 必须在 @Query 参数的前面。

1
2
@GET
Observable<User> login(@Url String url, @Query("username") String username);

接下来则可以改变所请求的 url 了,甚至可以指定到其它服务器上。

1
service.login("http://192.168.0.2:8080/v1/login", "Michael Cai")...;

对于 POST 请求,则没有参数定义位置的限制。

1
2
3
4
5
@POST
Observable<User> login(@Body User ur, @Url String url);
//
@POST
Observable<User> login(@Url String url, @Body User ur);

上面两种写法都没有问题,对于 POST 请求来说,使用 @Field@FieldMap 传参方式也是同样的。

String类型数据支持

在使用 GsonConverterFactory 之后,如果在一个接口中定义了 Observable\,则服务器返回的 String 字符串不会被正确处理,因为 GsonConveterFactory 会尝试把返回的字符串转换成 JSON_OBJECT,而不管接口中定义 String 数据类型。

可以对 retrofit 添加多个 Converter 来既支持 String,也支持 Gson

1
2
3
4
5
6
7
8
Retrofit retrofit2 = new Retrofit.Builder()
.baseUrl("http://10.50.40.53:8081/")
// 字符串 支持
.addConverterFactory(ScalarsConverterFactory.create())
// Gson 支持
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();

注意:ScalarsConverterFactory 需要在 GsonConverterFactory 的前面。

问题记录

  • url被转义

@PATH改成@Url

1
2
3
4
5
6
7
@GET
Call<User> getUser(@Url String url);}
或者
@GET("users/{user}")
Call<User> getUser(@Path(value = "user", encoded = true) String user);
  • PathUrl一起使用
1
2
@GET
Call<User> getUser(@Url String url, @Path("id") int id);

此时传递的url中需要指定占位符:users/ur{id}

Request <=> Response

AppClient Request —> Retrofit 2 –> Conveters –> Retrofit 2 —> OkHttp3 –> WebService handle Request
WebSevice Response –> OkHttp3 –> Retrofit2 –> Conveters –> Retrofit2 –> AppClient handle Response