说一下一年以来使用Retrofit
的心得和使用姿势.
- Retrofit 是什么:
A type-safe HTTP client for Android and Java
关于什么是REST ful架构, 这里不详细说明,其实我也不会详细说明,简单来说就是客户端通过四个HTTP 动词获取资源的过程
稍微说明一下HTTP报文的请求组成: 不论是请求报文还是响应报文
header
和body
无疑是最重要的两个部分.请求报文的header中会携带 Cookie,Cookie就是服务端来验证和区别客户端的手段,这个客户端是否是有效登陆的/是否是 成功注册过得等等. 响应报文中也会携带一些Cookie , 一些浏览器会使用一些方法储存和保护和使用这些Cookie . 在Retrofit 和 Okhttp中表现为CookieJar .什么是Okhttp: Okhttp 也是Retrofit的 开发公司Square的作品.Retrofit2封装了Okhttp的 一些东西,使得网络请求更加的方便快捷.Retrofit 和 Okhttp这两者是密不可分的.
进入正题:
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
基本配置.
发送一个GET请求总是很简单的,我使用官网的POST请求为例说明一下.(我稍微修改了一下)
这个逻辑是这样子的,如果向服务端发送一个Task的相关信息,服务端就会返回给你这个Task的一些相关的信息.
服务端返回的是JSON数据格式
第一步:
首先写一个接口 里面有这样的一个函数:
public interface TaskService {
@POST("/tasks")
Call<Task> createTask(@Body Task task);
}
根据JSON格式 手写一个这样的Java 类 叫做Task
public class Task {
private long id;
private String text;
public Task(long id, String text) {
this.id = id;
this.text = text;
}
}
因为我现在要进行一个POST请求,so:
Task task = new Task(1,"this is a simple task");
//使用Retrofit 的实例创建一个TaskService 的实例
TaskService taskService = retroft.createTask(TaskService.class);
Call<Task> call = taskService.createTask(task);
接着进行请求并且获取请求的结果:
//进行一个异步请求
call.enqueue(.....);
//或者进行一个同步请求
taskService.createTask(task).execute();
好的,以上就是这样:
但是
握草这些是什么鬼?看完了这一些代码片段之后一定有这些疑问:
1) @POST? @Body? 而且 @POST()括号里面的是什么?
2) JSON就JSON数据好了 直接写了一个类是什么意思? 我怎么取出数据呢?
3) 同步和异步又是什么鬼? 我是否需要 在主线程开一个线程进行网络请求?
4) 如果我需要登录header,我应该放在哪里呢?
5) 这段代码没有给出创建一个Retrofit实例的过程,Retrofit应该如何创建呢?
6) 为什么需要在一个接口中写这个函数?
来一步步解释一下(基本操作)
- 为什么需要在接口中写一个函数:
如果自己写一个函数来进行网络请求不就增加了难度,所以借助已经创建好了的Retrofit的实例来给出接口的实例来进行网络请求是最简洁的. - 如何创建一个Retrofit实例:
//interceptor 是 打印网络请求的log 并且设置log的层级 Level.BODY的层级是比较全面的
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(25, TimeUnit.SECONDS)
.connectTimeout(25, TimeUnit.SECONDS)
.writeTimeout(25,TimeUnit.SECONDS)
.addInterceptor(interceptor)
// .cookieJar(cookieJar)
.build();
//Retrofit实例的创建过程使用了建造者模式:
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.baseUrl("https://something.best.com/api/")
.build();
之前说过Retrofit
的基础是Okhttp
所以,构建Retrofit的时候需要传入一个OkhttpClient的实例 在这个client的实例中可以设置interceptor
和 CookieJar
在创建Retrofit的实例的过程中baseUrl()
就是需要请求的url的一部分, Retrofit
在请求的过程中会将@POST括号中的部分 拼接在baseUrl的后面形成一个完整的requestUrl.
这里@POST 是Retrofit的特色之一,使用注解来"标注"请求的类型: POST
GET
等 而@Body
表示后面的数据不是携带完整数据的一个类.
接口中的这个函数的返回值是一个被包括在Call<T>
(CallAdapter)这个泛型T所对应的实际类型,这里就是Task
注意:这里请求时上传的值和返回的类型都是一样的都是Task
,在其他情况可能不一样.
这里需要注意的是callAdapter可能不止一种,还有对RxJava的支持Observable<T>也是经常见到的.
但是返回的数据明明是JSON数据,为什么变成了一个类 ,这里就需要多谢之前配置的依赖converter-gson
提供的一个类GsonConverter,这个类将JSON数据转化成了一个类
{
"city":"北京",
"size":16800,
"population":1600
}
好比这一段json数据 如果你使用GsonFormat这个Android Studio 插件的话 就会生成
String city;
int size;
int population;
这三个量分别对应三个数据.
之后可以使用一些getter()
setter()
来取出数据.
在进行数据请求的时候也新建一个这样的类的实例的话,在构造函数通填入数据就可以了(有一种 key-value的感觉对吧!)
但是如果需要的不只是响应的body中的内容 还需要响应报文中的其他东西,可以使用Call<ResponseBody>
这样不止返回Body 还有其他内容,但是这样的话就没有通过converter
转化为一个类了; 同理,如果不想管返回类型,可以使用Call<Void>
此外Retrofit还提供了很多其他的注解@Query
/ @QueryMap
注解可以用于查询参数的场景
https://www.google.com/search?q=something&aqs=somethingelse&sourceid=chrome&ie=UTF-8
代码可以写成:
@Query("q") String arg1,@Query("ags") String args2, @Query("sourceid") String args3, @Query("id") String args4;
只需要key和查询参数的key一一对应就好了
还有@Path注解和其他注解 这里就不一一赘述了
如果使用的是enqueue()
函数的话 进行的是一个异步的操作,可以再回调函数中操作UI线程
Call<ResponseBody> call = mCcnuService.performLibLogin2();
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
这个是一个同步的操作 注意不要阻塞了主线程
retrofit2.Response<ResponseBody> responseBody2 = mCcnuService.performSystemLogin().execute();
最后说一下在使用过程中经常出现的问题和一些常见的使用场景的解决策略:
1) 需要访问url A 获取数据之后 在访问 url B 使用url A 里面拿到的数据访问 url C 获取最后想要的数据:
推荐使用Retrofit的同步操作,这样的话比较符合逻辑上面的顺次进行的概念 如果使用异步操作的话可能会造成回调地狱
- 为什么命名onSuccess执行了 但是没有取出想要的数据?
这个情况多半是在Call<T>中 T 类型的变量名 和JSON数据中的不一样. 这很好理解,key-value 如果key错误了就不能取出正确的数据了.
在使用@Header
注解的时候也要注意这个问题 token/Cookie的key也要和后端提供的一样.使用postman
调试过,拿到JSON数据在使用GsonFormat生成会大大避免这个问题