什么是 Retrofit ?
Retrofit是Square开发的一个Android和Java的REST客户端库。这个库非常简单并且具有很多特性,相比其他的网络库,更容易让初学者快速掌握。
Retrofit配置
在 app/build.gradle 添加依赖
在这里我们最好查看一下retrofit的官网添加最新依赖。
compile 'com.squareup.retrofit2:retrofit:2.2.0'
创建retrofit实例
String baseUrl = "http://192.168.1.8:8080/RetrofitService/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.build();
创建Retrofit实例时需要通过Retrofit.Builder,并调用baseUrl方法设置URL。
注: Retrofit2 的baseUlr 必须以 /(斜线) 结束,不然会抛出一个IllegalArgumentException,所以如果你看到别的教程没有以 / 结束。
接口定义
以获取指定id的News为例:
public interface NewsService {
//普通call方式
@GET("NewsServlet")
Call<ResponseBody> getNews(@Query("id") int id);
}
注意,这里是interface
不是class
,所以我们是无法直接调用该方法,我们需要用Retrofit创建一个NewsService
的代理对象。
NewsService newsService = createRetrofit().create(NewsService.class);
拿到代理对象之后,就可以调用该方法啦。
接口调用
Call<ResponseBody> answers = newsService.getNews(1);
answers.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
Retrofit注解详解
上面提到Retrofit 共22个注解,这节就专门介绍这22个注解,为帮助大家更好理解我将这22个注解分为三类,并用表格的形式展现出来,表格上说得并不完整,具体的见源码上的例子注释
第一类:HTTP请求方法
HTTP请求方法注解.png以上表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:method
、path
、hasBody
下面是用HTTP注解实现上面的例子。
/**
* method 表示请求的方法,区分大小写
* path表示路径
* hasBody表示是否有请求体
*/
@HTTP(method = "GET", path = "NewsServlet", hasBody = false)
Call<ResponseBody> getNews1(@Query("id") int id);
注:method
的值 retrofit 不会做处理,所以要自行保证其准确性,之前使用小写也可以是因为示例源码中的服务器不区分大小写,所以希望大家注意。
第二类:标记类
标记类注解.pngFormUrlEncoded
注解接口的2种写法
/**
* {@link FormUrlEncoded} 表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
* <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
*/
@POST("/NewsServlet")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded1(@Field("name") String name, @Field("age") int age);
/**
* Map的key作为表单的键
*/
@POST("/NewsServlet")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
分别对应调用的2种方法
第一种:
NewsService newsService = createRetrofit().create(NewsService.class);
Call<ResponseBody> answers = newsService.testFormUrlEncoded1("txy", 18);
第二种:
NewsService newsService = createRetrofit().create(NewsService.class);
HashMap<String, Object> map = new HashMap<>();
map.put("name", "txy");
map.put("age", 18);
Call<ResponseBody> answers = newsService.testFormUrlEncoded2(map);
Multipart
注解接口的3种写法,Multipart
一般是上传文件的时候使用
/**
* {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
* 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
*/
@POST("NewsServlet")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
/**
* PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
* 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
* 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
*/
@POST("NewsServlet")
@Multipart
Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);
@POST("NewsServlet")
@Multipart
Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
对应的3种调用方式
第一种:
NewsService newsService = createRetrofit().create(NewsService.class);
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "txy");
RequestBody age = RequestBody.create(textType, "18");
//构建要上传的文件
File file = new File(Environment.getExternalStorageDirectory(), "paoche.jpg");
RequestBody requestFile =
RequestBody.create(MediaType.parse("application/otcet-stream"), file);
MultipartBody.Part filePart =
MultipartBody.Part.createFormData("fileUploader", file.getName(), requestFile);
Call<ResponseBody> answers = newsService.testFileUpload1(name, age, filePart);
第二种:
NewsService newsService = createRetrofit().create(NewsService.class);
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "txy");
RequestBody age = RequestBody.create(textType, "18");
Map<String, RequestBody> fileUpload2Args = new HashMap<>();
fileUpload2Args.put("name", name);
fileUpload2Args.put("age", age);
//构建要上传的文件
File file = new File(Environment.getExternalStorageDirectory(), "paoche1.jpg");
RequestBody requestFile =
RequestBody.create(MediaType.parse("application/otcet-stream"), file);
MultipartBody.Part filePart =
MultipartBody.Part.createFormData("fileUploader", file.getName(), requestFile);
Call<ResponseBody> answers = newsService.testFileUpload2(fileUpload2Args, filePart);
第二种:
NewsService newsService = createRetrofit().create(NewsService.class);
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "txy");
RequestBody age = RequestBody.create(textType, "18");
Map<String, RequestBody> fileUpload3Args = new HashMap<>();
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);
第三类:参数类
参数类注解.png注1:{占位符}和PATH
尽量只用在URL的path部分,url中的参数使用Query
和QueryMap
代替,保证接口定义的简洁
注2: Query
、Field
和Part
这三者都支持数组和实现了Iterable
接口的类型,如List
,Set
等,方便向后台传递数组。
Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
//结果:ids[]=0&ids[]=1&ids[]=2
Field
、FieldMap
、Part
和PartMap
前面的例子已经讲过。
下面说一下Header
和Headers
的二种用法、静态和动态我自己理解的,看下接口怎么写
第一种静态方式:
//静态添加Header
@Headers("Cache-Control: max-age=640000")
@GET("NewsServlet")
Call<ResponseBody> testHeader1();
//静态添加多个Header
@Headers({"X-Foo: Bar","X-Ping: Pong"})
@GET("NewsServlet")
Call<ResponseBody> testHeader2();
第二种动态方式:
//动态添加Header
@GET("NewsServlet")
Call<ResponseBody> testHeader3(@Header("Cache-Control") int header, @Query("name") String name);
//动态添加Header
@GET("NewsServlet")
Call<ResponseBody> testHeader4(@HeaderMap Map<String, String> headers, @Query("name") String name);
Gson与Converter
在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody
,这也是什么我在前面的例子接口的返回值都是 Call<ResponseBody>
,但如果响应体只是支持转换为ResponseBody
的话何必要引用泛型呢,返回值直接用一个Call
就行了嘛,既然支持泛型,那说明泛型参数可以是其它类型的,而Converter
就是Retrofit为我们提供用于ResponseBody
转换为我们想要的类型,有了Converter
之后我们就可以写把我们的第一个例子的接口写成这个样子了:
public interface NewsService {
//普通call方式
@GET("NewsServlet")
Call<News> getNews(@Query("id") int id);
}
当然只改变泛型的类型是不行的,我们在创建Retrofit时需要明确告知用于将ResponseBody
转换我们泛型中的类型时需要使用的Converter
引入Gson支持:
compile 'com.squareup.retrofit2:converter-gson:2.0.1'
通过GsonConverterFactory为Retrofit添加Gson支持:
Gson gson = new GsonBuilder()
//配置你的Gson
.setDateFormat("yyyy-MM-dd hh:mm:ss")
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
//可以接收自定义的Gson,当然也可以不传
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
说道Gson我们就先聊一下有些Http服务返回一个固定格式的数据的问题。 例如:
{
"code": 0,
"message": "成功",
"data": {}
}
或:
{
"code": 0,
"message": "成功",
"data": []
}
不一样的地方就是data是一个对象或对象数组,这种情况下咱们怎么统一处理,我们可以创建一个BaseModel类
public class BaseModel<T> {
public int resultCode;
public String resultMessage;
public T data;
}
以demo返回的2种数据为例
data是普通对象
{"code":200,"data":{"comments":[{"content":"我是内容"}],"date":"20170603","id":1,"likes":"我喜欢新闻","title":"我是标题","views":"我是views"},"message":"获取成功!"}
以第一个例子为例咱们看下怎么修改,Call的泛型填写BaseModel<News>即可
public interface NewsService {
//普通call方式
@GET("NewsServlet")
Call<BaseModel<News>> testConverter1(@Query("id") int id);
}
data是普通对象数组
{"code":200,"data":[{"comments":[{"content":"我是内容"}],"date":"20170603","id":1,"likes":"我喜欢新闻","title":"我是标题","views":"我是views"}],"message":"获取成功!"}
Call的泛型填写BaseModel<List<News>>即可
public interface NewsService {
//普通call方式
@POST("NewsServlet")
Call<BaseModel<List<News>>> testConverter1();
}
结合我们现在用的只传JSON的请求,请求header
的content-type
是 application/json
,也就是@Body
的用法写一个列子:
@POST("NewsServlet")
Call<BaseModel<List<News>>> testConverte3(@Body News news);
被@Body
注解的的Blog将会被Gson转换成RequestBody
发送到服务器。
下面咱们看下接口怎么调用
NewsService newsService = createRetrofit().create(NewsService.class);
News news = new News();
news.likes = "likes";
news.date = "20170619";
news.title = "Converter用法3";
news.id = 1;
news.views = "views";
Call<BaseModel<List<News>>> call = newsService.testConverter3(news);
服务器收到的数据就是如下这样
{"comments":[],"date":"20170619","id":1,"likes":"likes","title":"Converter用法3","views":"views"}
RxJava与CallAdapter
说到Retrofit就不得说到另一个火到不行的库RxJava
,网上已经不少文章讲如何与Retrofit结合,但这里还是会有一个RxJava
的例子,不过这里主要目的是介绍使用CallAdapter
所带来的效果。
注意:下面说的RxJava
是指的RxJava2.0
第3节介绍的Converter
是对于Call<T>
中T
的转换,而CallAdapter
则可以对Call
转换,这样的话Call<T>
中的Call
也是可以被替换的,而返回值的类型就决定你后续的处理程序逻辑,同样Retrofit提供了多个CallAdapter
,这里以RxJava
的为例,用Observable
代替Call
:
引入RxJava支持:
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
// 针对rxjava2.x(adapter-rxjava2的版本要 >= 2.2.0)
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
通过RxJavaCallAdapterFactory为Retrofit添加RxJava支持:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
//可以接收自定义的Gson,当然也可以不传
.addConverterFactory(GsonConverterFactory.create(gson))
// 针对rxjava2.x
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
接口设计:
public interface NewsService {
@GET("NewsServlet")
Observable<BaseModel<List<News>>> testCallAdapter();
}
使用:
NewsService newsService = createRetrofit().create(NewsService.class);
Observable<BaseModel<List<News>>> observable = newsService.testCallAdapter();
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<BaseModel<List<News>>>() {
@Override
public void onSubscribe(Disposable d) {
//运行在io线程
}
@Override
public void onNext(BaseModel<List<News>> value) {
Log("RXJava用法 onNext:" + FastJsonUtils.toJson(value));
}
@Override
public void onError(Throwable e) {
Log("RXJava用法 onError:" + e.getMessage());
}
@Override
public void onComplete() {
Log("RXJava用法 onComplete");
}
});
结果:
{"code":200,"data":[{"comments":[{"content":"我是内容"}],"date":"20170603","id":1,"likes":"我喜欢新闻","title":"我是标题","views":"我是views"}],"success":true}
有的时候我们需要获取原始的json的字符串,GSON
是满足不了我们的,下面我们来看下怎么获取原始的json串,我们需要借助另一个Converter
,后面也会介绍都有哪些Converter
引入scalars支持:
compile 'com.squareup.retrofit2:converter-scalars:2.0.2'
通过ScalarsConverterFactory
为Retrofit
添加String
支持:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
//添加ScalarsConverterFactory支持
.addConverterFactory(ScalarsConverterFactory.create())
//可以接收自定义的Gson,当然也可以不传
.addConverterFactory(GsonConverterFactory.create(gson))
// 针对rxjava2.x
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
注意: 如果需要既支持String又支持Gson需要先设置ScalarsConverterFactory 后设置 GsonConverterFactory 如果上所示。
接口
@GET("NewsServlet")
Observable<String> testScalars();
看下怎么调用
Observable<String> observable = newsService.testScalars();
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String value) {
Log("Scalar用法 onNext value:" + value);
}
@Override
public void onError(Throwable e) {
Log("Scalar用法 onError " +e.getMessage());
}
@Override
public void onComplete() {
}
});
像上面的这2种情况最后我们无法获取到返回的Header
和响应码的,如果我们需要这两者,提供两种方案:
1、用Observable<Response<T>>
Observable<T>
,这里的Response
指retrofit2.Response
2、用Observable<Result<T>>
代替Observable<T>
,这里的Result
是指retrofit2.adapter.rxjava.Result
,这个Result
中包含了Response
的实例
看下第一种接口怎么设计
@GET("NewsServlet")
Observable<Response<BaseModel<List<News>>>> testRxAndroid2();
可以理解在以前BaseModel<List<News>>
的外面把包装一层Response
最终Response<BaseModel<List<News>>>
,接下来看下怎么调用
Observable<Response<BaseModel<List<News>>>> observable = newsService.testRxAndroid2(); observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<Response<BaseModel<List<News>>>>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Response<BaseModel<List<News>>> value) {
Headers headers = value.headers();
Set<String> names = headers.names();
Log("RXJava用法2 onNext value:" + FastJsonUtils.toJson(value.body()));
Log("RXJava用法2 onNext headers:" + FastJsonUtils.toJson(names));
BaseModel<List<News>> body = value.body();
Log("RXJava用法2 onNext body:" + FastJsonUtils.toJson(body));
}
@Override
public void onError(Throwable e) {
Log("RXJava用法2 onError:"+e.getMessage());
}
@Override
public void onComplete() {
Log("RXJava用法2 onComplete");
}
});
通过Response
的headers()
方法获取所有header
其它说明
Retrofit.Builder
前面用到了 Retrofit.Builder
中的baseUrl
、addCallAdapterFactory
、addConverterFactory
、build
方法,还有callbackExecutor
、callFactory
、client
、validateEagerly
这四个方法没有用到,这里简单的介绍一下。
| 方法 | 用途 |
| ------------- |-------------| -----|
| callbackExecutor(Executor) | 指定Call.enqueue
时使用的Executor
,所以该设置只对返回值为Call
的方法有效 |
| callFactory(Factory) | 设置一个自定义的okhttp3.Call.Factory
,那什么是Factory
呢?OkHttpClient
就实现了okhttp3.Call.Factory
接口,下面的client(OkHttpClient)
最终也是调用了该方法,也就是说两者不能共用|
| client(OkHttpClient) | 设置自定义的OkHttpClient
,以前的Retrofit
版本中不同的Retrofit
对象共用同OkHttpClient
,在2.0各对象各自持有不同的OkHttpClient
实例,所以当你需要共用OkHttpClient
或需要自定义时则可以使用该方法,如:处理Cookie、使用stetho 调式等 |
| validateEagerly(boolean) | 是否在调用create(Class)
时检测接口定义是否正确,而不是在调用方法才检测,适合在开发、测试时使用 |
Retrofit的Url组合规则
从上面不能难看出以下规则:
-
如果你在注解中提供的url是完整的url,则url将作为请求的url。
-
如果你在注解中提供的url是不完整的url,且不以 / 开头,则请求的url为baseUrl+注解中提供的值
-
如果你在注解中提供的url是不完整的url,且以 / 开头,则请求的url为baseUrl的主机部分+注解中提供的值
Retrofit提供的Converter
Converter | Gradle依赖 |
---|---|
Gson | com.squareup.retrofit2:converter-gson:2.0.2 |
Jackson | com.squareup.retrofit2:converter-jackson:2.0.2 |
Moshi | com.squareup.retrofit2:converter-moshi:2.0.2 |
Protobuf | com.squareup.retrofit2:converter-protobuf:2.0.2 |
Wire | com.squareup.retrofit2:converter-wire:2.0.2 |
Simple XML | com.squareup.retrofit2:converter-simplexml:2.0.2 |
Scalars | com.squareup.retrofit2:converter-scalars:2.0.2 |
Retrofit提供的CallAdapter
CallAdapter | Gradle依赖 |
---|---|
guava | com.squareup.retrofit2:adapter-guava:2.0.2 |
Java8 | com.squareup.retrofit2:adapter-java8:2.0.2 |
rxjava | com.squareup.retrofit2:adapter-rxjava:2.0.2 |
结语
本篇博客已经完成,后续我还会出进阶版本,包括调用https请求、下载文件、报文加密等等。最后感谢 怪盗kidou 老铁,本篇博客好多都是借鉴的这位兄弟的,添加了一些自己的理解。
网友评论