ps: 我写这篇这是很晚了,也是怪我自己啊,一直以来拖拖拉拉的就是改不了,retrofit 看着好几次了,看完有些日子就记不清了,也没记录下来,忘了都没地方找去。所以今天好好理一理 retrofit ,注解的使用还是很有必要记记的,谁也不敢打包票自己不忘是不,尤其是我,我对于这可是有深刻的印象啊,16年那会完全不写博客,看了好多东西,看完就忘,结果发现16年的收获惨不忍睹啊。
另外说一点,写博客真是对知识点的理解和记忆是大大的加深啊,所以大家总是说好记性不如烂笔头呀
retrofit 集成,简单使用
- 添加依赖
// retrofit 依赖
compile 'com.squareup.retrofit2:retrofit:2.4.0'
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
- 编写远程接口
retrofit 使用直接注解动态生成远程请求 API ,这让我们专注于接口声明,而不用去管理实现,既简单又明了
public interface BlueService {
@GET("book/search")
Call<ResponseBody> getSearchBooks(@Query("q") String name,
@Query("tag") String tag, @Query("start") int start,
@Query("count") int count);
}
- 原生简单请求
String baseUrl = "https://api.douban.com/v2/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.build();
BlueService blueService = retrofit.create(BlueService.class);
blueService.getSearchBooks("小王子", "", 0, 3)
.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
String message = response.body().string();
Log.d("AAA", "onResponse: " + message);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.d("AAA", "onFailure: 联网失败");
}
});
注意啊:
- baseUrl 跟路径必须以 “ / ” 结尾,现在强制要求了,不写会报错
- response.body().string() 可以获取原始的 response 数据(字符串),我们使用的 .string() 而不是 .toString() , toString 出来的 class 名字。同时返回参数数据类型我们必须使用 ResponseBody 这个才行,这是 okhttp3 里面的,这样才能拿到原始响应体
- 这个写法一般是获取 json 转 bean 或是测试通不通用的,正常我们不这么用
- 添加 json 数据变换
String baseUrl = "https://api.douban.com/v2/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
BlueService blueService = retrofit.create(BlueService.class);
blueService.getSearchBooks("小王子", "", 0, 3)
.enqueue(new Callback<BookResponse>() {
@Override
public void onResponse(Call<BookResponse> call, Response<BookResponse> response) {
try {
BookResponse bookResponse = response.body();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<BookResponse> call, Throwable t) {
Log.d("AAA", "onFailure: 联网失败");
}
});
这个我们在 retrofit.build 时添加了类型转换器,就可以让 retrofit 自动帮我们转换 json 了,然后远程接口,callback 我们都可以直接使用具体的数据类型了。我们还是 try 一下保险
- 添加 rxjava 支持,变换请求结果为 observable
public interface BlueService {
@GET("book/search")
Observable<BookResponse> getSearchBooks(@Query("q") String name,
@Query("tag") String tag, @Query("start") int start,
@Query("count") int count);
}
String baseUrl = "https://api.douban.com/v2/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
BlueService blueService = retrofit.create(BlueService.class);
blueService.getSearchBooks("小王子", "", 0, 3)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<BookResponse>() {
Disposable disposable;
@Override
public void onSubscribe(Disposable d) {
disposable = d;
}
@Override
public void onNext(BookResponse bookResponse) {
Log.d("AAA", "onNext: " + bookResponse.getBooks().size());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
disposable.dispose();
}
});
}
要注意 rxjava2 的支持和1不同,rxjava2 的支持请安上面的依赖添加
中间插一段 http 协议基础
我也是个搬运工,http 协议我摘一点作用明显的,大家看看,然后觉得不怎么懂就去专门看下,我在下面会附上地址的
HTTP 的请求报文分为三个部分 请求行、请求头和请求体,格式如图:
- 请求行
请求行(Request Line)分为三个部分:请求方法、请求地址和协议及版本,HTTP/1.1 定义的请求方法有8种:
- GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,最常的两种GET和POST
- RestFul 规范的接口的话会用到 GET、POST、DELETE、PUT,分别对应数据库的增删改查操作:
- PUT -> 增
- DELETE -> 删
- POST -> 改
- GET -> 查
在了解请求地址之前,先了解一下URL的构成:
所以我们在 retrofit 的 baseUrl 添加跟地址的行为就是添加 http 协议和主机地址
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
剩下的 http 基础内容还有很多,我就不复制了,大家看这里:你应该知道的HTTP基础知识,一定要看哦,要不后面你会晕,有地方搞不明白的
- 请求头
其中有一点我们一定要弄清楚的是 http 请求体类型,也就是我们会看到的 Content-Type
- Content-Type:application/json
表示这是提交的 json - Content-Type:application/x-www-form-urlencoded
表单提交 - Content-Type:multipart/form-data; boundary={boundary}
文件提交,也叫模拟表单
重头戏来啦,注解详解
整篇文章的重点就在这啦,前面的都是浮云,初次使用的同学可能觉得有些繁琐,有些不好理解,但是对于有点水平的同学们来说就不叫事了不是,所x以注解这个才是我们需要重点学习的,看了上面的 http 协议的部分知识点,我们再去研究下 retrofit 的注解就不是那么挠头了
1. 请求类型
请求类型是请求头中的一个内容,写在 header 里,字段是:Content-Type,是描述我们给服务器发送的数据采用什么格式发送,下面放个例子:
interface APIStore {
@Headers({"Content-Type: application/json","Accept: application/json"})
@POST ("vdyweb/ws/rest/Login")
Call<ResponseBody>getMessage(@Body RequestBody info);
}
大家看到在请求头中添加了 Content-Type 字段的数据,这里总结下有几种请求方式啊,这点很重要的,写不对你的远程通讯就会不同,或是服务器拿不到数据哦。retrofit 已经为我们封装好了常用的请求方式,这里结合的说一下:
- 什么都不写
什么都不写表示传递参数没有规范,发送过去的是二进制流,传值你得用 @Body 注解,服务器接受到的是二进制流,然后可以转成字符串按 json 解析数据,不过一般我么不推荐这么写。 - json 提交
json 的话我司现在就是用这种方法,适合做 post 万能请求接口,不过在 RestFul 规范的今天, 不在推荐这么写了,我们也得让后台兄弟舒服不是。所以我们在 retrofit 找不到这个注解,就是因为不推荐这种写法了, 下面是自己写的方式,这里只是接口声明,详细在后面
interface APIStore {
@Headers({"Content-Type: application/json","Accept: application/json"})
@POST ("vdyweb/ws/rest/Login")
Call<ResponseBody>getMessage(@Body Book book);
}
@Body 的数据对象,Retrofit2 会自动转成 Gson 字符串发送
- get 方式
get 方式很特殊,大家都知道 get 请求方式很特殊,把一切都拼接在 url 地址中,所以没那么复杂, Content-Type 不用设置 - 表单提交
表单提交,数据都以 key—value 的形式存在请求体中,服务器获取参数也是用 key—value 的形式去拿数据,作为最常使用的提交方式,retrofit 已经给我们封装好了,专门有一个字段
@FormUrlEncoded
Observable<BookResponse> getSearchBooks(@Query("q") String name);
自己写的话就是
@Headers("Content-Type:application/x-www-form-urlencoded")
Observable<BookResponse> getBooks();
- 模拟表单
在表单提交的基础上可以添加文件数据,retrofit 也给我们封装好了
@Multipart
Observable<BookResponse> getBooks();
自己写的话就是
@Headers("Content-Type:multipart/form-data; boundary={boundary}")
Observable<BookResponse> getBooks();
boundary 部分可写可不些,我看有人不写 boundary 也没事,boundary=xxxxxxx,xxxxxx规定了请求体中的内容分隔符
-
文档相关资料
2. 请求方法
请求方法上面 http 基础应说过了,这个不难理解,需要注意的是 restFul 规范下的部分:
* PUT -> 增
* DELETE -> 删
* POST -> 改
* GET -> 查
这里要提一下 http 这个请求方法,HTTP注解则可以代替任意一个青谷去方法,有3个属性:method,path,hasBody,下面是例子:
public interface BlogService {
/**
* method 表示请的方法,不区分大小写
* path表示路径
* hasBody表示是否有请求体
*/
@HTTP(method = "get", path = "blog/{id}", hasBody = false)
Call<ResponseBody> getFirstBlog(@Path("id") int id);
}
3. 数据接收类型
在 http 协议中可以指定接受服务器返回数据的类型,这个一般我们不用自己去设置,retrofit 默认就是 json 格式的,但是自己也可以设,使用的是上面可以看到的 Accept 这个字段
@Headers({"Content-Type: application/json","Accept: application/json"})
写在 head 请求头了,看到这大家可以发现,这些设置除了 get/post 之外,所有的请求设置都是写在请求头里的,请求头就是干这事的,类似于我们常用的 config ,包裹配置参数和一些通用参数。
retrofit 还提供了 @Streaming 这个注解,我们接收的数据不再是 json 的了,而是一个二进制输入流。
@Streaming
4. 传参相关参数
retrofit 用来在不同请求方法中传递参数的注解都在上面了,单说没意思,结合 get / post 和请求不同的数据来说最简单明了,容易懂。
5. get 传参详解
@GET("xxxUrl")
Observable<User> getUserInfo(
@Part("xxxUrl") String userUrl,
@Query("userid") int id,
@QueryMap Map<String, String> options,
@Query("list") List<String> options2);
可以看到 get 可以接受4种传参注解:
- @Part -1
用来替换具体的 utl 路径的,一般我们都是直接就写 url 地址了,这里通过 @Part 提供一种动态传入 url 地址方式,@Part 注解是所有请求方法通用的。 - @Part -2
上面的 @Part 写法是整体替换具体的 URL 请求,其实要是后台同学的 restFul API 写的规范的话,我们请求,操作数据的 URL 也是可以做到动态的,不是费的一个接口写一个固定的地址给我们。这时 @Part 可以用来实现动态替换 URL 中参数的事,使用 {“xxx”} 来标记 URL 中可变的部分,下面就是一个例子:
@GET("book/{id}")
Observable<User> getBook(@Part("id") String id);
- @Query
传单个参数,Query("userid") 里面的字符串是 key,后面指定 value 的类型 - @QueryMap
可以接受多个参数,所有参数直接写在 map 里 - Query("list")
get 方式支持直接传入一个集合,可以看到其实和我们单个传参一样,只不过区别是接收的数据是单个对象,还是一个集合对象
6. post 传参详解
@POST("xxxUrl")
Observable<User> getUserInfo(
@Field("userid") int id,
@FieldMap Map<String, String> options,
@Field("list") List<String> options2);
post 传参其实和 get 差不多,却别不多,一个是 query ,一个是 field
- @Field
传单个参数,和 @Query 一样 - @FieldMap
传多个数据 - @Field("list")
同样支持直接传递集合数据类型 - @POST
@POST 除了get 都能用,用来传递一个数据对象,服务器按 stream 流的方式接收数据
@POST("name")
Observable<User> getName(@Body Book book);
7. 上传文件
上传文件一直是个有问题的地方,不管是 Xutils,okhttp,retrofit 都有不是很明确的地方,这里就能看出网络基础是多么重要,有时候你写完之后,服务器接收不到参数,或是问你发的数据用的什么类型,我怎么接受,你都回答不上来,这个很尴尬不是,我是遇到过,还好后台的兄弟没打我脸,哈哈哈哈.......
不看我写的,看看这些文章也是可以的:
有一点我们要说,表单提交的时候参数的编码问题 enctype属性。enctype:规定了form表单在发送到服务器时候编码方式,他有如下的几个值:
- application/x-www-form-urlencoded
默认值,不写就是这个,retrofit 的表单默认也是这个,不能用来传文件,也就是二进制数据 - multipart/form-data
所有的数据以二进制流发送 - application/otcet-stream
很奇怪,我查了查是八进制.........这个恕我真的不知道为啥有二进制不用,去用八进制 - text/plain
纯文本格式
在带有 file 的表单提交中:
- file 只能用流的形式上传
- 可以用:multipart/form-data / application/otcet-stream 这2种流上传,差别估计是后台接受数据的 API 不同。
- 文本参数可以用流的方式上传,也可以用文本格式上传
- 可以使用 multipart/form-data 这样后台用流接数据,getParame 拿不到数据的。
- 最好用 text/plain 文本格式传,这样后台获取数据和原来的方式相同。
好了看过这些,我们心里总算是对这些弯弯绕的东西有些初步了解了,再去和后台沟通也大概知道怎么说了,查资料也知道去找什么东东了
@Multipart
@POST("upload")
Call<ResponseBody> uploadMultipleFiles(
@Part("description") RequestBody description,
@PartMap Map<String, RequestBody> options
@Part MultipartBody.Part file1,
@Part MultipartBody.Part file2);
这是一个带其他参数的文件上传 API ,大家注意看注解如何使用,这个是固定的,大家背下来就行。期中 @Part("description") 里面的字符串是普通文本参数的 key
我们来看看重点的 java 代码:
- 创建文本参数
注意这里我用的是 text/plain 发送文本参数,用 multipart/form-data 二进制的方式也可以的
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), "文本参数");
- 创建 file 参数
File imageFile = new File("一张图片");
RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), imageFile);
MultipartBody.Part data = MultipartBody.Part.createFormData("file", imageFile.getName(), requestBody);
- 文件数据传入 map 集合中的写法
可能我们的接口是这样的
@POST("NewsServlet")
@Multipart
Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> map);
所有参数都放在这个集合中,集合参数的放置如下,注意 file 类型的参数的 key 要包含 file 的name 进去,需要自己拼接字符串的
NewsService newsService = createRetrofit().create(NewsService.class);
Map<String, RequestBody> fileUpload3Args = new HashMap<>();
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "txy");
RequestBody age = RequestBody.create(textType, "18");
fileUpload3Args.put("name", name);
fileUpload3Args.put("age", age);
//构建要上传的文件
File file = new File(Environment.getExternalStorageDirectory(), "paoche1.jpg");
RequestBody requestFile =
RequestBody.create(MediaType.parse("application/otcet-stream"), file);
fileUpload3Args.put("fileUploader\"; filename=\"paoche3.jpg",requestFile);
Call<ResponseBody> answers = newsService.testFileUpload3(fileUpload3Args)
上传文件基本就是这些了,这块容易忘,容易混,记下来非常有必要的。
8. 上传图片
上传图片看这篇也行,总结的也很好,比我全面一点:
图片也是文件,但是为啥要单独拿出来说呢,因为图片有自己单独的数据格式 : image/png
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), imageFile);
MultipartBody.Part photo = MultipartBody.Part.createFormData("上传的key", "文件名.png", photoRequestBody);
9. 添加请求头参数
请求头参数我们可以在网络请求的 API 接口里写,也可以给 okhttp 添加一个拦截器进来,在请求构建完成,发送前的那一刻拦截,然后添加请求头数据,这样适合添加动态可变参数
- @Headers 添加静态请求头参数
public interface BlueService {
@Headers("Cache-Control: max-age=640000")
@Headers({
"Accept: application/vnd.yourapi.v1.full+json",
"User-Agent: Your-App-Name"
})
@GET("book/search")
Call<BookSearchResponse> getSearchBooks(@Query("q") String name,
@Query("tag") String tag, @Query("start") int start,
@Query("count") int count);
}
注意添加一条和多条的区别
- @Header 添加动态请求头参数
public interface BlueService {
@GET("book/search")
Call<BookSearchResponse> getSearchBooks(
@Header("Content-Range") String contentRange,
@Query("q") String name, @Query("tag") String tag,
@Query("start") int start, @Query("count") int count);
}
- 添加拦截器动态添加请求头参数
public class HeadInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("User-Agent", "Your-App-Name")
.header("Accept", "application/vnd.yourapi.v1.full+json")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
}
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new HeadInterceptor ())
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
添加 header 参数 Request 提供了两个方法:
- header(key, value)
如果有重名的将会覆盖 - ddHeader(key, value)
允许相同 key 值的 header 存在
10. 为某个请求设置完整的URL
有个别的请求的 URL 不用用的我们基础的 baseurl 的,怎么办,我们可以使用 @Url 注解来忽略 baseurl 的
@GET
public Call<ResponseBody> profilePicture(@Url String url);
Retrofit retrofit = Retrofit.Builder()
.baseUrl("https://your.api.url/");
.build();
BlueService service = retrofit.create(BlueService.class);
service.profilePicture("https://s3.amazon.com/profile-picture/path");
json 上传的再次说明
json 上传数据不推荐,但是有时候我们真的需要,这里多记录一下找到的东西。
json 除了我们直接 @Body Book book 直接写具体的数据类型的做法,我们也可以使用 RequestBody 来写
public interface PostRoute {
@Headers({"Content-Type: application/json","Accept: application/json"})
@POST("api/FlyRoute/Add")
Call<FlyRouteBean> postFlyRoute(@Body RequestBody route)RequestBody
}
// 先 json 一个字符串数据出来
Book book= new Book ();
Gson gson=new Gson();
String route = gson.toJson(book);
// 我们生成一个 RequestBody 请求对象出来
PostRoute postRoute=retrofit.create(PostRoute.class);
RequestBody body=RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),route);
Call<Book> call=postRoute.postFlyRoute(body);
添加日志管理
这个是 okhttp 的部分,但是呢我们不打算专门写一个 okhttp 的入了,在 retrofit 中我们已经充分使用了 okhttp 了,索性就一起写了
retrifit 又开源的日志拦截器,要不我们就自己写拦截器打印日志,不过肯定大的不如直接诶个开源的全啊。
添加依赖
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
使用
// 创建日志拦截器对象出来
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
//包含header、body数据
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
// 添加日志拦截器到 okhttp 对象中
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build();
// 把 okhttp 对象添加到 retrofit 对象中
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
打印样式图:
注意里面 --> 、<-- 箭头的方向:
- --> 表示我们请求的内容,包括请求头参数,请求信息等,这里我们是最简单的 get 请求所以表现不出来
- <-- 是我们接受到的内容,包括接受数据形式
- interceptor 有几个级别,我们用 body 就行,body 会代印包括头在内的所有信息
facebook 网络调试器 Stetho
我还看到有一位朋友提到 facebook 开源了 Stetho 网络监测工具,试了试我没成功,有兴趣的朋友请看:使用OkHttp高效开发调试
其他内容
- retrofit 对于 https 的支持
这块我现在也是看看,没实践过
对于 https 证书有两种策略:
根据自己的证书服务器来配置,达到一对一的效果,每个商业app都应该有自己的证书设置,这样能保证访问的安全性。
在 okhttp 中设置信任所有证书,参考:okhttp3证书解决方式
-
请求加密
我项目中加密是用 MD5 把字符串里面的参数算一下,然后添加到请求头里,服务器拿到后也计算一下然后核对 MD5 加密字段,无误后再去执行。-
Retrofit 2.0 详解(三)报文加解密 拦截器(Interceptor)
这个思路是给获取请求体字符串,添加加密字段 - Retrofit 2.0 超能实践(一),完美支持加密Https传输
-
Retrofit 2.0 详解(三)报文加解密 拦截器(Interceptor)
-
下载文件
参考资料
- 你应该知道的HTTP基础知识,一定要看哦,要不后面你会晕,有地方搞不明白的
- Retrofit2 学习总结
- Retrofit用法详解
- 使用OkHttp高效开发调试
- 如何通过Retrofit提交Json格式数据
- Retrofit 2.0 详解(一)基本用法