在 Android 开发中,有多种网络请求框架,从一开始自己写”框架”,到后面使用第三方框架,接触下来个人最喜欢的是 Retfofit,然而 Retrofit 并不是网络请求框架,可以把它看作是对网络请求接口进行优化配置的一个框架,真正的网络请求处理是它所依赖的 OkHttp处理的,本篇主要记录使用 Retrofit2 + RxJava 框架来进行网络请求的一些方法。
配置 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 ,需要拆分成两部分:
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 ,还需要使用 RxJava2CallAdapterFactory 将 User 对象进行封装返回;
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 组合 Path 、Query
1 2 3 4 5 6 7 8 9 10 11 12
| @GET("users/{user}") Observable<Data<User>> ex_Path(@Path("user") String user); @GET("users") Observable<Data<User>> ex_Query(@Query("user") String user); @GET("users") Observable<User> login(@Query("userIds") List<String> userIds);
|
Query可以是单个字符串对象,也可以是一个集合。
Path 表示请求路径中 user 部分是动态替换的。
Path和Query还可以组合在一起使用:
1 2 3 4 5 6 7 8 9 10 11
| @GET("users/list?sort=desc") Observable<Data<User>> ex_Query(); @GET("users/{name}") Observable<Data<User>> ex_PathWithQuery(@Path("name") String name, @Query("sort") int sort); @GET("users/{name}") Observable<Data<User>> ex_PathWithQueryMap(@Path("name") String name, @QueryMap Map<String, String> kvs);
|
POST 组合 Field 、FieldMap
1 2 3 4 5
| @FormUrlEncoded @POST("users/edit") Observable<Data<User>> ex_Field(@Field("first_name") String first);
|
1 2 3
| @FormUrlEncoded @POST("users/edit") Observable<User> login(@FieldMap HashMap<String, String> map);
|
1 2 3
| @POST(ServiceName + "login") Observable<User> login(@Body User user);
|
注: GET不能使用Body参数,Body表示的是传递的请求内容,在GET请求中是没有的。
HTTP、Body
1 2 3 4 5 6 7 8 9 10 11
| @HTTP(method = "POST", path = "users/new", hasBody = true) Observable<Data<User>> ex_Body_Object(@Body User ur); @HTTP(method = "POST", path = "users/new", hasBody = true) Observable<Data<User>> ex_Body_Map(@Body Map<String, String> map);
|
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(); @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
| @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
之前的配置方式,所要访问的 baseUrl 和 apiPath 都是固定的,除了固定的配置方法,还可以使用 @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()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();
|
注意:ScalarsConverterFactory 需要在 GsonConverterFactory 的前面。
问题记录
将@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);
|
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