相信大家都听过Retrofit的大名但是没有实际运用,或是已经运用过Retrofit1.x,因为Retrofit1.x和Retrofit2.x差别非常大,Retrofit1.x教程也是非常多,为了简单易懂,所以本文将以最新Retrofit2实践运用满足我们的Retrofit日常开发,后续我们也会更深入的了解Retrofit2,最后在本文中的尾页将附上Demo。
在阅读过程中有任何问题,请及时联系。如需转载请注明 fuchenxuan de blog
Retrofit 是一个Square开发的类型安全的REST安卓客户端请求库。这个库为网络认证、API请求以及用OkHttp发送网络请求提供了强大的框架 。Retrofit 可以利用接口,方法和注解参数来声明式定义一个请求应该如何被创建。并且可更换或自定义HTTP client,以及可更换或自定义Converter,返回数据解析方式。Retrofit可用于Android和Java的一个类型安全(type-safe)的REST客户端,如果你的服务器使用的使RESTAPI,那么你将非常适合使用它。
请选择以下三种方式中一种进行安装,最后如果你正在使用PROGUARD,请添加下方PROGUARD配置。
关于Retrofit源代码以及官方简单例子,请访问http://github.com/square/retrofit
如果你正在使用GRADLE在你的项目中的build.gradle
添加以下代码到您的配置:
compile 'com.squareup.retrofit2:retrofit:2.1.0'
如果你正在使用MAVEN在你的项目中的pom.xml
添加以下代码到您的配置:
<dependency>
<groupId>com.squareup.retrofit2groupId>
<artifactId>retrofitartifactId>
<version>2.1.0version>
dependency>
如果你正在使用PROGUARD在你的项目中添加以下代码到您的配置:
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
在使用Retrofit前,我们需要先创建Retrofit实例,并且做一系列配置,然而Retrofit设计的也是非常好,这些配置都是可插拔的:
Retrofit retrofit = new Retrofit.Builder()
//设置baseUrl,注意baseUrl 应该以/ 结尾。
.baseUrl("http://news-at.zhihu.com/api/4/")
//使用Gson解析器,可以替换其他的解析器
.addConverterFactory(GsonConverterFactory.create())
//设置OKHttpClient,如果不设置会提供一个默认的
.client(new OkHttpClient())
// .client(new UrlConnectionClient())
// .client(new ApacheClient())
// .client(new CustomClient())
.build();
Retrofit 背后的 HTTP client,以及序列化机制(JSON/XML 协议)等都是可以替换,因此你可以选择自己合适的方案。Retrofit 最早出来的时候,只支持 Apache 的 HTTP client。后来增加了 URL connection,以及 OkHttp 的支持。如果你想使用其他的 HTTP client,可以通过以下方式了替换,或者更改为自定义的HTTP client:
//设置OKHttpClient,如果不设置会提供一个默认的OkHttpClient
.client(new OkHttpClient())
// .client(new UrlConnectionClient())
// .client(new ApacheClient())
// .client(new CustomClient())
序列化功能也是可替换的。默认是用的 GSON,你当然也可以用 Jackson 来替换掉。
//使用Gson解析器,可以替换其他的解析器
.addConverterFactory(GsonConverterFactory.create())
//当需要返回原始String数据时
.addConverterFactory(ScalarsConverterFactory.create())
除此之外Retrofit还提供以下几种Converter:
com.squareup.retrofit2:converter-gson
com.squareup.retrofit2:converter-jackson
com.squareup.retrofit2:converter-moshi
com.squareup.retrofit2:converter-protobuf
com.squareup.retrofit2:converter-wire
com.squareup.retrofit2:converter-simplexml
com.squareup.retrofit2:converter-scalars
我们将看一下如何用Retrofit与服务器交互,通过它你将学会如何运用Retrofit于日常开发。
Retrofit使用接口,方法和参数,使用注解表明了请求将如何处理,每一种方法都必须有一个HTTP标注提供请求的方法和相对URL,有五种内置注解:GET
, POST
, PUT
, DELETE
, 和 HEAD
,在注解中指定URL
。请选择以下方式中合适的请求方式来处理您的请求。
在这里我们最开始第一个GET请求使用的是知乎日报的api,为了更好使用Retrofit其他请求方式而又没有比较好的公开api,我自行编写了配合使用Retrofit的测试服务端,放置在外网以便大家测试使用。
基于上面的最初的步骤,接下来我们需要定义一个接口,并且使用注解(@GET
)表明一次GET请求:
public interface ZhihuService {
//获取启动页大图
@GET("start-image/1080*1776")
Call getStartImage();
}
这是一个普通的GET请求,接着我们来看如何利用Retrofit 创建服务接口,并且设置参数:
ZhihuService messageService = retrofit.create(ZhihuService.class);
Call startImage = messageService.getStartImage();
最后,使用startImage.enqueue
进行异步请求,并且获取了我们期待的数据(实体对象):
startImage.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
Log.d(TAG, response.body().toString());
resultTextView.setText("" + response.body().toString());
}
}
@Override
public void onFailure(Call call, Throwable t) {
resultTextView.setText("" + "error:" + t.getMessage());
}
});
如果你想使用call.execute()
进行同步请求,需要注意的是不要放在UI线程:
try{
Response response = call.execute(); // 同步
Log.d(TAG, "response:" + response.body().toString());
} catch (IOException e) {
e.printStackTrace();
}
因为一次call.execute() 的request只能执行一次,否则你将会得到如下错误:
java.lang.IllegalStateException: Already executed
如果你想取消本次请求可以使用 startImage.cancel()
或者是复制一次request,再次请求:
startImage.cancel();//取消
Call cloneRequsest = startImage.clone();//复制
是不是感觉特别简单,使用时候只需调用接口,这一切都简化了我们的操作。
我们需要先定义一个接口,并且使用注解(@Query
)或者是@QueryMap
表明动态参数请求如何处理:
相应的URL是这样:
http://baseurl/app/test/sayHello?username=fuchenxuan&age=110
@GET("test/sayHello")
Call<String> sayHello(@Query("username") String username, @Query("age") String age);
接着我们忽略接口的创建,直接使用Retrofit与服务器交互,值得注意的是我们此次返回的数据是String,而不是一个自定义的实体类对象。所以我们需要更换Converter,否则你将会遇到不必要的麻烦(而我觉得Retrofit应该提供一个默认Stirng的实现):
//当需要返回原始String数据时
.addConverterFactory(ScalarsConverterFactory.create())
接着我们就得到了我们期待的数据:
Call<String> doubanCall = myTestApiService.sayHello("fuchenxuan", "110");
doubanCall.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
if (response.isSuccessful()) {
Log.d(TAG, response.body().toString());
resultTextView.setText("" + response.body().toString());
}
}
@Override
public void onFailure(Call<String> call, Throwable t) {
}
});
GET 动态PATH 就是优雅的RESTful api方式,
相应的URL是这样:
http://news-at.zhihu.com/api/4/start-image/1024*782
@GET("start-image/{size}")
Call getStartImageByPath(@Path("size") String size) ;
form-data 就是如表单K-V参数形式
这里其实就跟GET的动态参数是一致的只是替换了@POST
注解
public interface MyTestApiService {
@POST("test/sayHello")
Call postSayHello(@Query("username") String username, @Query("age") String age);
}
当服务器需要你POST 参数以json打包数据格式请求时,然而这种参数方式RESTful api 也是非常常见的,我们需要使用@Body
注解:
@POST("test/sayHi")
// @Headers("Accept-Encoding: application/json")
//使用@Headers 可添加header
Call postSayHi(@Body UserBean userBean);
上面我们还示例了如何使用@Headers
@Headers("Accept-Encoding: application/json")
添加头部信息,或者我们有需求需要使用@Header
实现动态头部信息:
@POST("test/sayHi")
@Headers("Accept-Encoding: application/json")
//也可以使用@Header 可添加header
Call postSayHi(@Body UserBean userBean, @Header("city") String city);
前面也说了retrofit非常适用于RESTful url的格式,这里因为知乎的就是RESTful api,我们直接使用和GET动态URL一样的注解(@PATH
)来表明请求处理:
相应的URL是这样:
http://news-at.zhihu.com/api/4/start-image/1024*782
@POST("start-image/{size}")
Call getStartImageByPath(@Path("size") String size) ;
在我们开发当中肯定必不可少图片上传了,我们使用表单上传文件时,必须让 表单的
enctyped
等于 multipart/form-data
。
文件上传我们需要使用@MultiPart
和@Part
,MultiPart意思就是允许多个@Part
多部分上传。
@Multipart
@POST("test/upload")
Call upload(@Part("file\"; filename=\"launcher_icon.png") RequestBody file) ;
值得注意的是我们需要在@Part
指定file和filename的值,避免一些不必要的麻烦。
相应的我们在使用retrofit的时候,首先先获取到文件,并且创建RequestBody实例,然后调用接口请求,相应代码块如下:
File file = new File(getExternalFilesDir(null), "launcher_icon.png");
RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), file);
Call doubanCall = myTestApiService.upload(fileBody);
doubanCall.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
Log.d(TAG, response.body().toString());
resultTextView.setText("" + response.body().toString());
}
}
@Override
public void onFailure(Call call, Throwable t) {
// Log.d(TAG, response.body().toString());
resultTextView.setText("" + "error:" + t.getMessage());
}
});
文件下载我们需要使用@Url
和 @Streaming
,@Url
动态Url正好非常适合我们的场景,而使用@Streaming
注解可以让我们下载非常大的文件时,避免Retrofit将整个文件读进内存,否则可能造成OOM现象。
声明接口如下:
@Streaming
@GET
Call<ResponseBody> downloadFileByDynamicUrlAsync(@Url String downloadUrl);
需要注意的是我们需要使用Retrofitcall.execute
同步获取ResponseBody,那么我们就需要放进一个单独的工作线程中:
new AsyncTask() {
@Override
protected Void doInBackground(Void... voids) {
Call call = myTestApiService.downloadFileByDynamicUrlAsync(API_BASE_URL.concat("/res/atom-amd64.deb"));
try {
Response response = call.execute();
boolean writtenToDisk = writeResponseBodyToDisk(response.body());
Log.d(TAG, "下载文件 " + writtenToDisk);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
}.execute();
最后我们需要将文件写入磁盘根目录中:
//写入到磁盘根目录
private boolean writeResponseBodyToDisk(ResponseBody body) {
try {
File futureStudioIconFile = new File(Environment.getExternalStorageDirectory() + File.separator + "atom.deb");
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
final long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
final long finalFileSizeDownloaded = fileSizeDownloaded;
runOnUiThread(new Runnable() {
@Override
public void run() {
resultTextView.setText("file download: " + finalFileSizeDownloaded + " of " + fileSize);
}
});
}
outputStream.flush();
return true;
} catch (IOException e) {
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
这样我们就可以非常高效的下载大文件了,最后友情提醒(如果是6.0以上另外再申请权限):
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
尽管文章前面已经把Retrofit2注解基本了解完了,但是有必要多了解一些其他的注解,或许您正好有这样的场景需求,他们分别可作用于方法和参数:
@Headers
:用于在方法添加请求头: @POST("test/sayHi")
@Headers("Accept-Encoding: application/json")
Call<ResultBean> postSayHi(@Body UserBean userBean, @Header("city") String city);
@Streaming
@Streaming
@Streaming
@GET
Call<ResponseBody> downloadFileByDynamicUrlAsync(@Url String fileUrl);
@Header
:用于在方法参数里动态添加请求头:Call postSayHi(@Header("city") String city);
@Body
:用于Body的JSON格式参数 @POST("test/sayHi")
Call<ResultBean> postSayHi(@Body UserBean userBean);
@Url
:
使用动态的请求的网址,会复写之前的baseUrl,值得注意的是@Url
需要在所有参数之前:
@POST
Call postSayHelloByURL(@Url String url,@Query("username") String username, @Query("age") String age) ;
@Path
:
主要适合用于RESTful api ,动态URL,比如https://baseUrl/api/v1/
,将API的版本号放入URL,变换Path切换不同版本的API。
@POST("/api/{version}/{size}")
Call getStartImageByPath(@Path("size") String size,@Path("version") String version) ;
以下几个都是用于服务器接受表单参数类型(form-data)时使用
@Filed
@FiledMap
这两个需要和@FormUrlEncoded
配合使用,参数形式体现在请求体上:
@FormUrlEncoded
@POST("test/sayHello")
Call postSayHelloByForm(@Field("username") String username, @Field("age") String age) ;
@Query
@QueryMap
:
这两个和@Filed
、@FiledMap
功能是一致的,区别在于参数形式体现在URL上,类似这样:http://baseurl/app/test/sayHello?username=fuchenxuan&age=110
的URL
@GET("test/sayHello")
Call<String> sayHello(@Query("username") String username, @Query("age") String age);
@Part
@PartMap
:
这两个用于上传文件,与@MultiPart
注解结合使用
@Multipart
@POST("test/upload")
Call upload(@Part("file\"; filename=\"launcher_icon.png") RequestBody file) ;
如果你已经完全读完了这篇文章并且也实践着编写了这个程序,那么你一定已经能够非常熟练自如地使用Retrofit2了,你可能也已经编写了一些Retrofit2程序来尝试练习各种Retrofit2特性。如果你还没有那样做的话,那么你一定要快点去实践。
本文末尾将略带一下Retrofit2其他特性,后续文章我们也会有更伟大的实践。
Retrofit2提供了三个CallAdapter:
rxjava:com.squareup.retrofit2:adapter-rxjava
你肯定很期待将和与Java8 或者是rxjava结合使用,当然你也可以自定义CallAdapter,满足自己的需求。那么我们将会在下篇文章中一起学习这个快乐的编程。
Retrofit2 使用Demo.zip
Retrofit2 简明教程(一)就到这了,文章中的示例代码在下面,如有问题欢迎在下方留言,及时让我知道,写一篇好的技术blog好难,但您的支持是我的动力,希望您在下方的留言让我做的更好。
作者:fuchenxuan
出处:http://blog.csdn.net/vfush
欢迎访问我的个人站点:http://fuchenxuan.cn
转载请注明出处–http://blog.csdn.net/vfush