Retrofit使用指南
Retrofit is a type-safe HTTP client for Android and Java.
Retrofit是面向Android和Java平台的一个类型安全的HTTP客户端。
本文将围绕Retrofit的注解、CallAdapter(配合RxJava)、Converter进行介绍;
首先在你的工程添加以下依赖:
final OKHTTP_VERSION = '3.4.1'
final RETROFIT_VERSION = '2.1.0'
compile "com.squareup.okhttp3:okhttp:$OKHTTP_VERSION"
compile "com.squareup.okhttp3:logging-interceptor:$OKHTTP_VERSION"
compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION"
compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION"
版本号如有更新的可以更改到最新
接下来演示如何请求接口:
假设我们现在需要请求一个这样的接口: https://api.github.com/users/yuhengye
首先创建一个TestService接口
import retrofit2.Call;
import retrofit2.http.GET;
public interface TestService {
//定义一条接口请求
@GET("users/yuhengye")
Call<String> getUserInfo();
}
先不关心为何创建一个这样的接口
接下来创建一个RetrofitManager.class类
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitManager {
private static Retrofit mRetrofit;
private static TestService mTestService;
private static OkHttpClient mOkHttpClient;
public static OkHttpClient getOkHttpClient(){
if(mOkHttpClient == null) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
mOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(logging)
.build();
}
return mOkHttpClient;
}
public static Retrofit getRetrofit(){
if(mRetrofit == null) {
mRetrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(getOkHttpClient())
.build();
}
return mRetrofit;
}
public static TestService getTestService(){
if(mTestService == null) {
mTestService = getRetrofit().create(TestService.class);
}
return mTestService;
}
}
接下来看下如何在代码里请求接口:
1.异步执行:
Call<String> call = RetrofitManager.getTestService().getUserInfo();
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
//获得结果
String result = response.body();
}
@Override
public void onFailure(Call<String> call, Throwable t) {
}
});
2.同步执行
Call<String> call = RetrofitManager.getTestService().getUserInfo();
try {
Response<String> response = call.execute();
//获得结果
String result = response.body();
}catch (IOException ioException){
ioException.printStackTrace();
}
上面例子中的Call<String>里申明的call是用于操控网络请求的,而申明的泛型String类型这是我们想要获得请求结果时转换成的类型,下文会解释是怎么转换的;
在Android里不能在主线程执行请求网络的操作,所以使用同步请求是需要另起线程,所以一般都是异步请求;
如果你像以上所述操作后,那么你已经成功使用Retrofit请求网络。
接下来看下如何定义一条接口请求:
//定义一条接口请求
//等价于https://api.github.com/users/yuhengye
@GET("users/yuhengye")
Call<String> getUserInfo();
可以看到方法头部使用了注解,官方文档是这么解释的:
Every method must have an HTTP annotation that provides the request method and relative URL. There are five built-in annotations: GET, POST, PUT, DELETE, and HEAD. The relative URL of the resource is specified in the annotation.
每个方法都必须有一个HTTP注释提供的请求方法和相对URL。有五个内置注释:GET,POST,PUT,DELETE和HEAD。资源的相对URL在注释中指定。
也就是说如果你想用GET请求就可以在方法头部加@GET
注释,使用POST请求就使用@POST
即可,在注释里申明的相对URL会和在生成Retrofit实例时使用的baseUrl()
方法里传的值结合;
Retrofit提供不同HTTP请求方法的注解:GET
,POST
,PUT
,DELETE
,HEAD
,OPTIONS
,PATCH
,HTTP
;
Retrofit还提供了以下注解:Path
,Header
,HeaderMap
,Headers
,Query
,QueryMap
,FormUrlEncoded
,Field
,FieldMap
,Multipart
,Part
,PartMap
,Body
,Streaming
,Url
;
值得一提的是,Retrofit里提供的注解命名规范是这样的,如果是HTTP请求的方法,则一律是大写,其他注解才使用驼峰式;
接下来解释下这些注解如何使用,为了缩短篇幅,以下在调用接口时,就不写获取接口实例的方法了,比如调用getUserInfo()
相当于RetrofitManager.getTestService().getUserInfo()
,并且申明的接口方法都是在TestService接口下;
Path
@GET("users/{username}")
Call<String> getUserInfo(@Path("username") String name);
//等价于https://api.github.com/users/yuhengye
getUserInfo("yuhengye");
可以看出Path
是用于替换相对路径url里的值;
Header,HeaderMap,Headers
//在方法里声明多个参数时,可以多种注解组合使用
@GET("users/{username}")
Call<String> getUserInfo(@Header("Accept-Language") String lang, @Path("username") String name);
//Request Header
//Accept-Language:en-US
getUserInfo("en-US", "yuhengye");
---------------divider---------------
@GET("users/yuhengye")
Call<String> getUserInfo(@Header("Accept-Language") List<String> langs);
List<String> langs = new ArrayList<>();
langs.add("en-US");
langs.add("zh-CN");
//Request Header
//Accept-Language:en-US,zh-CN
getUserInfo(langs);
---------------divider---------------
@GET("users/yuhengye")
Call<String> getUserInfo(@HeaderMap<String,String> headers);
Map<String,String> headers = new HashMap<>();
header.put("Accept", "text/plain");
header.put("Accept-Charset", "utf-8");
//Request Header
//Accept:text/plain
//Accept-Charset:utf-8
getUserInfo(headers);
---------------divider---------------
@Headers("Accept-Language: en-US")
@GET("users/yuhengye")
Call<String> getUserInfo();
@Headers({
"Accept-Language: en-US,zh-CN",
"Cache-Control: max-age=640000"
})
@GET("users/yuhengye")
Call<String> getUserInfo2();
Header
是用于给HTTP的请求头增加信息的,HeaderMap
则是当有多个Header和数量不固定的Header准备的;下面介绍Query
和QueryMap
,Field
和FieldMap
时也是一样的道理;而Headers
是当你的接口的Header是固定的时候才用到,并且支持多个(数组形式);
Query,QueryMap
@GET("search/repositories")
Call<String> searchRepo(@Query("q") String keywords, @Query("sort") String sort);
//等价于https://api.github.com/search/repositories?q=retrofit&sort=stars
searchRepo("retrofit", "stars");
---------------divider---------------
@GET("search/repositories?order=desc")
Call<String> searchRepoOrderByDesc(@QueryMap Map<String,String> params);
Map<String,String> params = new HashMap<>();
header.put("q", "retrofit");
//等价于https://api.github.com/search/repositories?order=desc&q=retrofit
searchRepo(params);
Query
和QueryMap
主要是在GET请求时,给你的URL传参,当然URL的参数也可以固定在相对路径里面;
FormUrlEncoded,Field,FieldMap
@FormUrlEncoded
@POST(“https://api.weibo.com/oauth2/access_token”)
Call<String> oauthToken(@Field("access_token") String code, @Field("client_secret") String client_secret);
//POST表单数据
oauthToken("codeValue", "secretValue");
这里没有使用到FieldMap
,相信你也知道它的用处是什么了,当你需要使用到表单的方式请求时,只需使用@FormUrlEncoded
注解就可以了,实际上跟你替换成@Headers("Content-Type: application/x-www-form-urlencoded")
同理,只是框架已经帮我们实现了,Field
和FieldMap
跟Query
和QueryMap
类似,只不过前者是在表单提交时使用的注解,后者是URL传参时使用的注解。
在上面例子中,@Post
中的URL不再是相对路径,而是绝对路径,当你使用绝对路径时,Retrofit就会忽略掉你实例化它时通过baseUrl()
方法传给它的host(接口前缀地址);因为实际开发中,有可能只有几个接口的host是跟你设置的host不一样的,为了避免实例化多个Retrofit,你只需传入绝对路径即可;
Body
@POST("users/new")
Call<String> createUser(@Body RequestBody user);
---------------divider---------------
@POST("users/new")
Call<String> createUser(@Body User user);
当发起POST请求时,使用@Body
把参数直接放到你的request的body里面,如果在实例化Retrofit时,没有指定converter(下文会解释这是什么), 那@Body
注解后面的参数类型必须是RequestBody类型;
Multipart,Part,PartMap
@Multipart
@POST(“https://api.weibo.com/2/statuses/upload_pic.json”)
Call<String> uploadPicture(
@Part("access_token") RequestBody token,
@Part MultipartBody.Part file);
RequestBody requestFile = RequestBody.create(MediaType.parse("application/otcet-stream"), new File(s));
MultipartBody.Part body = MultipartBody.Part.createFormData("pic", "share.png", requestFile);
uploadPicture(RequestBody.create(MediaType.parse("multipart/form-data"), "tokenValue"), body);
以下是POST请求时,multipart
的body内容
//部分头信息
Content-Type: multipart/form-data; boundary=d1547544-7ffb-4ebd-913e-8cb21d2ea8f9
Content-Length: 32461
//body内容开始
--d1547544-7ffb-4ebd-913e-8cb21d2ea8f9
Content-Disposition: form-data; name="access_token"
Content-Transfer-Encoding: binary
Content-Type: multipart/form-data; charset=utf-8
Content-Length: 32
2.00tJ6X3GiGSdcC5f7433b5a40iAPJA
--d1547544-7ffb-4ebd-913e-8cb21d2ea8f9
Content-Disposition: form-data; name="pic"; filename="share.png"
Content-Type: application/otcet-stream
Content-Length: 31813
file bytes
--d1547544-7ffb-4ebd-913e-8cb21d2ea8f9--
//body内容结束
通常我们上传文件时,大多数使用multipart
,下面简单描述下multipart
的传输过程:
每一个部分都是以--加boundary(分隔符)开始,然后是该部分内容的描述信息,然后一个回车,然后是描述信息的具体内容;如果传送的内容是一个文件的话,那么还会包含文件名信息,以及文件内容的类型。上面请求的第二个部分就是是一个文件体的结构,最后会以--boundary符--结尾,表示请求体结束。
当使用@Multipart
时,相当于@Headers("Content-Type: multipart/form-data; boundary=${bound}")
,并且框架帮你把boundary(分隔符)的值设定好了;
@Part
和@PartMap
是配合@Multipart
使用的,使用@PartMap
参数类型必须是Map,key就相当于partName, value所接受的参数类型跟@Part
一样,但建议不要是MultipartBody.Part(因为MultipartBody.Part本身已经包含partName),以下是@Part
接受不同参数时的情况:
1.当方法里传参类型是MultipartBody.Part时,不要这样(@Part("partName") MultipartBody.Part part
申明,partName会被忽略,因为在构建MultipartBody.Part时,已经包含了part的name在里面;
2.当方法里传参类型是RequestBody时,会把RequestBody的ContentType的值加到body里面去,比如像上面例子这样申明Call<String> uploadPicture(@Part("access_token") RequestBody token);
那么调用该方法uploadPicture(RequestBody.create(MediaType.parse("multipart/form-data"), "tokenValue"))
时插入body里面的内容是这样的:
--d1547544-7ffb-4ebd-913e-8cb21d2ea8f9
Content-Disposition: form-data; name="access_token"
Content-Transfer-Encoding: binary
Content-Type: multipart/form-data; charset=utf-8
Content-Length: 32
2.00tJ6X3GiGSdcC5f7433b5a40iAPJA
--d1547544-7ffb-4ebd-913e-8cb21d2ea8f9--
3.当方法里传参类型是其他对象类型时,将会通过实例化Retrofit时指定的conveter(下文会解释这是什么)来转换成RequestBody,然后跟上面第二点所述处理;
Streaming
@Streaming
@GET("video/test.mp4")
Response getVideo();
Response response = getVideo();
InputStream videoDataStream = response.getBody().in();
使用@Streaming
时返回值必须是Response,包含了HTTP请求最初的body内容,未经任何转换;一般是获取比较大的数据流时使用,或者下载文件时使用;
Url
//等价于https://api.github.com/users/yuhengye
@GET("users/yuhengye")
Call<String> getUserInfo();
---------------divider---------------
@GET
Call<String> getUserInfo(@Url String url);
//等价于https://api.github.com/users/yuhengye
getUserInfo("users/yuhengye);
从上面可以看出,如果方法里申明带@Url
的参数,则在方法头部申明HTTP请求方式时,可以不再申明相对路径地址;
实例化Retrofit时调用baseUrl(api)
方法传进去的api地址应该以/
结尾,如果传的是https://api.github.com/search
,Retrofit会以/
最后出现的位置作为结尾当成https://api.github.com/
处理,值得一提的是,在申明请求接口头部定义url的时候,建议统一用相对路径(前面不包括/
),类似@GET("users/yuhengye")
而不是@GET("/users/yuhengye")
,因为这两者处理上也有所不同:
BaseUrl:https://api.github.com/search/
Endpoint:/users/yuhengye
Result:https://api.github.com/users/yuhengye
BaseUrl:https://api.github.com/search/
Endpoint:users/yuhengye
Result:https://api.github.com/search/users/yuhengye
CallAdapter
上文在申明接口的方法时,返回值统一都是Call<String>
类型,因为默认情况下返回值是只支持Call
类型的,这个类包含了最基本的网络请求操作,相信你看过很多文章是XXX App,使用了以下框架:Retrofit + RxJava + ...
,如果你没用过RxJava
,可以跳过此段介绍,下面简单介绍RxJava
结合Retrofit
请求网络。
首先加入以下依赖:
compile "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_VERSION"
//以下是下面例子中使用RxJava的版本
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
在实例化Retrofit时,改成这样:
public static Retrofit getRetrofit(){
if(mRetrofit == null) {
mRetrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(getOkHttpClient())
.build();
}
return mRetrofit;
}
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
这个是我们增加的调用,以后在申明接口的方法返回值时,就可以这样:
@GET("users/yuhengye")
Observable<String> getUserInfo();
RetrofitManager
.getTestService()
.getUserInfo()//返回一个Observable<String>对象
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String result) {
}
});
Converter(转换器)
上文中多次提到过converter的概念,现在简单介绍下:
Retrofit框架默认是使用OKHTTP去请求网络的,而在OKHTTP中网络请求默认都是返回一个ResponseBody
类型的值,为了免去网络请求中重复的转换操作,在实例化Retrofit时可以通过addConverterFactory(GsonConverterFactory.create())
方法增加一个转换工厂,Retrofit内置的转换工厂是只支持ResponseBody
,Void
类型,值得一提的是,在接口申明的返回值里如果不关心返回结果,必须这样申明Call<Void>
,不能没有泛型Call
这样申明;
文章开始介绍要引入的依赖时,有包括这个compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION"
, 这个库的的名字是converter-gson
,那么肯定是跟gson相关的了,回想一下,刚才我们申明接口方法时的返回值,统一都是Call<String>
,之所以可以申明String类型,是因为我们通过这个依赖库提供的Gson转换工厂把结果转换成了String类型,然后请求返回结果后得到一个Response<String>
类型的实例response
,Response
申明的泛型类型是跟Call
申明的泛型类型一致的,而String result = response.body()
就是获得申明泛型类型的值,通常来说我们是知道请求接口会返回的数据格式,如果是json
格式,每次都是通过获得String
类型,再转换成对应的实体类,那么会做很多重复的工作;所以上文在接口申明方法时的泛型类型都可以替换成对应的实体类,比如这样:
//GitHubUser就是通过gson转换成对应的实体类;
@GET("users/yuhengye")
Call<GitHubUser> getUserInfo();
当然Converter还不止是在请求结果返回时转换结果,还包括发起请求时的一些数据转换等;以上所提到的@HeaderMap
、@QueryMap
、FieldMap
后面申明的参数类型必须是Map,但是key和value没有限定必须是String类型,这3个注解和@Path
、@Header
、@Query
、@Field
默认最终都会调用内置的converter调用String.valueOf(object)
转成String类型,也就是调用Object的toString()
方法;
我们也可以自定义自己的Converter工厂,本文就不再叙述了,后面会再写一遍关于Retrofit的源码和原理探索还有进阶使用;
相关文章:
Retrofit源码解析
参考文档:
Retrofit官方文档
http://square.github.io/retrofit/
HTTP协议之multipart/form-data请求分析
http://blog.csdn.net/five3/article/details/7181521/
原文链接:http://www.jianshu.com/p/b6e68ac96f78
Retrofit使用指南的更多相关文章
- 收藏的技术文章链接(ubuntu,python,android等)
我的收藏 他山之石,可以攻玉 转载请注明出处:https://ahangchen.gitbooks.io/windy-afternoon/content/ 开发过程中收藏在Chrome书签栏里的技术文 ...
- Retrofit结合RxJava使用指南
Retrofit结合RxJava使用指南 Retrofit是一个当前很流行的网络请求库, 官网的介绍是: "Type-safe HTTP client for Android and Jav ...
- 【Android实战】----从Retrofit源代码分析到Java网络编程以及HTTP权威指南想到的
一.简单介绍 接上一篇[Android实战]----基于Retrofit实现多图片/文件.图文上传中曾说非常想搞明确为什么Retrofit那么屌. 近期也看了一些其源代码分析的文章以及亲自查看了源代码 ...
- [置顶]
【Android实战】----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的
一.简介 接上一篇[Android实战]----基于Retrofit实现多图片/文件.图文上传中曾说非常想搞明白为什么Retrofit那么屌.最近也看了一些其源码分析的文章以及亲自查看了源码,发现其对 ...
- Android最佳实践指南
Updated on 2016/1/6 修正了一些翻译段落欢迎转载,但请保留译者链接:http://www.jianshu.com/p/613d28a3c8a0 Lessons learned fro ...
- [翻译]现代java开发指南 第三部分
现代java开发指南 第三部分 第三部分:Web开发 第一部分,第二部分,第三部分 =========================== 欢迎来到现代 Java 开发指南第三部分.在第一部分中,我们 ...
- 浅谈 RxAndroid + Retrofit + Databinding
http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0131/3930.html 最近 RxAndroid .MVP.MVVM 一直是 And ...
- 你真的会用Gson吗?Gson使用指南(4)
原文出处: 怪盗kidou 注:此系列基于Gson 2.4. 本次文章的主要内容: TypeAdapter JsonSerializer与JsonDeserializer TypeAdapterFac ...
- 转载:Google 官方应用架构的最佳实践指南 赞👍
官方给的实践指南,很有实际的指导意义, 特别是对一些小公司,小团队,给了很好的参考意义. 原文地址: https://developer.android.com/topic/libraries/ar ...
随机推荐
- 算法生成N芒星
前面两个图像生成算法是:道教的太极八卦图和佛教的卐和卍字图.这一节整个洋气的图像:芒星.但愿我别召唤出什么恐怖的禁忌,尤其今晚还是万圣节之夜.平时看玄幻小说,经常读到有关六芒星,七芒星,九芒星的技法. ...
- UML图与软件开发过程那点关系
首先,软工文档, 软工文档,也就是计划,设计,描述,使用软件的一些文件,它最大的特点就是固定不变,用来给不同的人和计算机来阅读.在期间,文档起到了桥梁的作用,看这张图很形象: 在这里在看一下国家统一规 ...
- Service 简介 启动方式 生命周期 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- 【Error】centos7 minimal connect: Network is unreachable
参考链接:http://www.centoscn.com/CentosBug/osbug/2015/1208/6500.html 由于centos7 和之前的版本差异比较大,之前的一些命令不能完全使用 ...
- Backbone.js 中使用 Model
前面几篇 Backbone.js 的例子中有使用到 template, 及数据的填充,其实这已经很接近 Model 了.现在来学习怎么创建自己的 Model 类,并简单的使用.Backbone.js ...
- 泊松分布E(X^2)
由于求期望实际就是求平均值,所以E(X^2)=E[X*X]=E[X*X]+E(X)-E(X)=E[X*X+X-X]=E[X(X-1)+X]E[X(X-1)+X]=E[X(X-1)]+E(X)即:和的平 ...
- Android WindowManager实现悬浮窗效果 (一)——与当前Activity绑定
最近有学生做毕业设计,想使用悬浮窗这种效果,其实很简单,我们可以通过系统服务WindowManager来实现此功能,本章我们来试验一下在当前Activity之上创建一个悬浮的view. 第一步:认识W ...
- python3 杀死进程
在windows10杀死进程:Shadowsocks.exe os.system('taskkill /f /im %s' % 'Shadowsocks.exe')
- Java对象的序列化和反序列化源码阅读
前言 序列化和反序列化看起来用的不多,但用起来就很关键,因为稍一不注意就会出现问题.序列化的应用场景在哪里?当然是数据存储和传输.比如缓存,需要将对象复刻到硬盘存储,即使断电也可以重新反序列化恢复.下 ...
- 【流处理】Kafka Stream-Spark Streaming-Storm流式计算框架比较选型
Kafka Stream-Spark Streaming-Storm流式计算框架比较选型 elasticsearch-head Elasticsearch-sql client NLPchina/el ...