前言
时至今日,Android的网络框架不再像之前那么到处都是,随着Google把 HttpClient直接删掉,似乎意味着Android越来越成熟。网络框架中的佼佼者Volley也不再那么光鲜,取而代之的是 Retrofit 和 okHttp。如今不会使用Retrofit + okHttp + RxJava等一系列技术,就迈不进新时代的门槛,跟不上时代的步伐。
1. Retrofit介绍
A type-safe HTTP client for Android and Java
一个用于Android和Java平台的类型安全的网络框架
Retrofit is a type-safe REST client for Android built by Square. The library provides a powerful framework for authenticating and interacting with APIs and sending network requests with OkHttp.
Retrofit 是一个Square开发的类型安全的REST安卓客户端请求库。这个库为网络认证、API请求以及用OkHttp发送网络请求提供了强大的框架 。
You’ll use annotations to describe HTTP requests, URL parameter replacement and query parameter support is integrated by default. Additionally, it provides functionality for multipart request body and file uploads.
你可以使用注释来描述HTTP请求,URL参数替换和查询参数都默认支持。此外,它还支持多请求体和文件上传功能。
Retrofit 把REST API返回的数据转化为Java对象,就像ORM框架那样,把数据库内的存储的数据转化为相应的Java bean对象。那么Retrofit是一个类型安全的网络框架,而且它是使用REST API的,接下来我们看看什么是REST吧。
2. REST 介绍:
Resources Representational State Transfer
资源表现层状态转化
REST 指的是一组架构约束条件和原则,满足这些约束条件和原则的应用程序或设计就是 RESTful。REST 描述了一个架构样式的互联系统(如 Web 应用程序)。REST 约束条件作为一个整体应用时,将生成一个简单、可扩展、有效、安全、可靠的架构。
知道了REST是什么,那接下啦就开始介绍Retrofit的用法啦。
retrofit2官网地址:https://github.com/square/retrofit/
3. Retrofit基本用法
1.在build.gradle中添加依赖
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
目前最新版为2.1.0, 同时为了支持将网络请求转化成java bean对象,我们这里使用了gson,所以也需要在gradle里添加依赖。当然除了gson以外,还提供了以下的选择:
Gson - com.squareup.retrofit2:converter-gson
Jackson - com.squareup.retrofit2:converter-jackson
Moshi - com.squareup.retrofit2:converter-moshi
Protobuf - com.squareup.retrofit2:converter-protobuf
Wire - com.squareup.retrofit2:converter-wire
Simple Framework - com.squareup.retrofit2:converter-simpleframework
Scalars - com.squareup.retrofit2:converter-scalars
LoganSquare - com.github.aurae.retrofit2:converter-logansquare
FastJson - org.ligboy.retrofit2:converter-fastjson or org.ligboy.retrofit2:converter-fastjson-android
当然也支持自定义,你可以选择自己写转化器完成数据的转化,这个后面将具体介绍。2.创建接口,声明API
public interface GitHubUserInfo {
@GET("users/{user}")
Call<User> getUserInfo(@Path("user") String user);
}
这里我们以获取github开放的用户信息https://api.github.com/users/xxx 为例,注意retrofit能把接口返回的json直接转成bean对象,所以我们还需自己定义了User对象。这里略过User对象的代码。可以看到上面有一个getUserInfo()方法,通过@GET
注解标识为get请求,@GET
中所填写的value和baseUrl
将组成成完整的路径,baseUrl
在构造retrofit对象时给出。Retrofit提供了5种内置的注解:GET
、POST
、PUT
、DELETE
和HEAD,
在注解中指定URL的路径和查询参数,上面的{user}代表动态替换块,可以在使用时通过getUserInfo方法中@Path注解的参数替换,注意使用@Path修饰的参数必须与{}里的参数一样,这里都是“user”字符串。
3. 创建Retrofit
/**
* 创建Retrofit
*/
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/") //设置baseUrl,注意,baseUrl必须后缀"/"
.addConverterFactory(GsonConverterFactory.create()) //添加Gson转换器
.build();
4. 获取GitHubUserInfo的实例
/**
* 获取GitHubUserInfo的实例
*/
GitHubUserInfo userInfo = retrofit.create(GitHubUserInfo.class);
ok,这里很神奇,我们通过Retrofit.create
就可以拿到我们定义的GitHubUserInfo的实例,调用其方法即可拿到一个Call
对象,通过call.enqueue
即可完成异步的请求。
5.异步调用
/**
* 异步调用
*/
Call<User> userCall = userInfo.getUserInfo("nickyangjun");
userCall.enqueue(new Callback<User>(){
@Override
public void onResponse(Call<User> call, Response<User> response) {
Log.i(TAG, "name: " + response.body().login + " id: " + response.body().id);
}
@Override
public void onFailure(Call<User> call, Throwable t) {
}
});
需要注意的是每一个Call实例都可以同步call.excute()或者异步call.enquene(CallBack<?> callBack)的被执行,每一个实例仅仅能够被使用一次,但是可以通过clone()函数创建一个新的可用的实例。而且异步调用时上面的执行结果是在主线程中回调的,这点很方便我们更新UI操作。Retrofit默认是使用OKHttp网络请求框架来实现的网络请求操作的。并且2.0版本中支持请求取消,取消请求只需调用call.cancel()。
4. Retrofit注解
上面只是简单演示了Retrofit的get请求功能,同时上面也用到了@Path注解,Retrofit的请求注解还有许多更强大的功能,比如:
1 @Query
查询参数的设置,先看下面的url:
http://baseurl/users?sortby=username
http://baseurl/users?sortby=id
即一般的传参,我们可以通过@Query注解方便的完成,我们再次在接口中添加一个方法:
@GET("users")
Call <List<User>> getUserInfoBySort(@Query("sortby") String sort);
这里我们返回的是一个list<User>对象。调用时也跟前面调用方法一样:
//省略前面retrofit构建的代码
GitHubUserInfo userInfo = retrofit.create(GitHubUserInfo.class);
Call <List<User>> usersListCall = userInfo.getUserInfoBySort("id");
//省略后面call执行的代码
这样我们就完成了参数的指定,当然相同的方式也适用于POST,只需要把注解修改为@POST即可。对了,我上面学了@PATH,那么会不会有这样尝试的冲动,对于刚才的需求,我们这么写:
@GET("users?sortby={sortby}")
Call<List<User>> getUsersBySort(@Path("sortby") String sort);
看上去没有问题,哈,实际上运行是不支持的~估计是@ Path的定位就是用于url的路径而不是参数,对于参数还是选择通过@Query来设置。
2 @Body
POST请求体的方式向服务器传入json字符串,
大家都清楚,我们app很多时候跟服务器通信,会选择直接使用POST方式将json字符串作为请求体发送到服务器,那么我们看看这个需求使用retrofit该如何实现。
再次添加一个方法:
@POST("add")
Call<User> addUser(@Body User user);
调用post提交的代码基本都是一致的:
//省略前面构建retrofit的代码
GitHubUserInfo userInfo = retrofit.create(GitHubUserInfo.class);
Call <User> addUser = userInfo.addUser(new User(123,"nick"));
//省略后面Call执行的代码
ok,可以看到其实就是使用@Body这个注解标识我们的参数对象即可,那么这里需要考虑一个问题,retrofit是如何将user对象转化为字符串呢?下文将详细解释~
3 @FormUrlEncoded
表单的方式传递键值对
这里我们模拟一个登录的方法,添加一个方法:
@POST("login")
@FormUrlEncoded
Call<User> login(@Field("username") String username, @Field("password") String password);
提交的代码:
//省略前面构建retrofit的代码
GitHubUserInfo userInfo = retrofit.create(GitHubUserInfo.class);
Call <User> loginUser = userInfo.login("nick","123456");
//省略后面Call执行的代码
ok,看起来也很简单,通过@POST
指明url,添加FormUrlEncoded
,然后通过@Field
添加参数即可。
4 @Multipart
单文件上传
下面看一下单文件上传,依然是再次添加个方法:
@Multipart
@POST("register")
Call<User> registerUser(@Part MultipartBody.Part photo, @Part("username") RequestBody username,
@Part("password") RequestBody password);
这里@MultiPart的意思就是允许多个@Part了,我们这里使用了3个@Part,第一个我们准备上传个文件,使用了MultipartBody.Part类型,其余两个均为简单的键值对。
下面是调用:
//省略前面构建retrofit的代码
GitHubUserInfo userInfo = retrofit.create(GitHubUserInfo.class);
File file = new File(Environment.getExternalStorageDirectory(), "icon.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("photos", "icon.png", photoRequestBody);
Call<User> call = userInfo.registerUser(photo, RequestBody.create(null, "abc"),
RequestBody.create(null, "123"));
//省略后面Call执行的代码
ok,这里感觉略为麻烦。不过还是蛮好理解~~多个@Part,每个Part对应一个RequestBody。
5 @PartMap
多文件上传,再添加一个方法~~~
@Multipart @POST("register")
Call<User> registerUser(@PartMap Map<String, RequestBody> params);
这里使用了一个新的注解@PartMap,这个注解用于标识一个Map,Map的key为String类型,代表上传的键值对的key(与服务器接受的key对应),value即为RequestBody,有点类似@Part的封装版本。
执行的代码:
//省略前面构建retrofit的代码
GitHubUserInfo userInfo = retrofit.create(GitHubUserInfo.class);
File file = new File(Environment.getExternalStorageDirectory(), "icon.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
Map<String,RequestBody> photos = new HashMap<>();
photos.put("photos\"; filename=\"icon.png", photoRequestBody);
photos.put("username", RequestBody.create(null, "abc"));
photos.put("password", RequestBody.create(null, "123"));
Call<User> call = userInfo.registerUser(photos);
//省略后面Call执行的代码
可以看到,可以在Map中put进一个或多个文件,键值对等,当然你也可以分开,单独的键值对也可以使用@Part,这里又看到设置文件的时候,相对应的key很奇怪,例如上例"photos\"; filename=\"icon.png",前面的photos就是与服务器对应的key,后面filename是服务器得到的文件名,ok,参数虽然奇怪,但是也可以动态的设置文件名,不太影响使用~~
6
请求头的设置可以通过
设置静态的请求头。 @Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-Sample-App" })
@GET("users/{user}")
Call<User> getUserInfo(@Path("user") String user);
动态的设置请求头。
@GET("user")
Call getUser(@Header("Authorization") String authorization);
注意,同一个请求的同一个请求头在不同地方的设置不会被覆盖,而是会被全部添加进请求头中。
如果要给每个请求都添加同样的Header时,可以使用okHttp的 。
5 配置OkHttpClient
由于Retrofit 2.0 底层强制依赖okHttp,所以可以使用okHttp的拦截器Interceptors 来对所有请求进行再处理。目前使用中,一般用来、 、 等。这里介绍下配置OkHttpClient和设置UserAgent的Interceptor:
public class OkHttpFactory {
private OkHttpClient client;
private static final int TIMEOUT_READ = 25;
private static final int TIMEOUT_CONNECTION = 25;
Cache cache = new Cache(MyApplication.mContext.getCacheDir(), 10 * 1024 * 1024);//缓存目录
private OkHttpFactory(){
client = new OkHttpClient.Builder().cache(cache)
.addInterceptor(new LoggingInterceptor()) //打印log
.addInterceptor(new UserAgentInterceptor()) //UserAgent设置
.retryOnConnectionFailure(true) //失败重连
.readTimeout(TIMEOUT_READ, TimeUnit.SECONDS)
.connectTimeout(TIMEOUT_CONNECTION, TimeUnit.SECONDS)
.build();
}
public static OkHttpClient getOkHttpClient(){
return Holder.INSTANCE.client;
}
private static class Holder{
final public static OkHttpFactory INSTANCE = new OkHttpFactory();
}
}
这里用单例实现了OkHttpClient,并且添加了缓存和超时设置,下面看看UserAgentInterceptor拦截器代码:
public class UserAgentInterceptor implements Interceptor {
private static final String USER_AGENT_HEADER_NAME = "User-Agent";
private final String userAgentHeaderValue;
public UserAgentInterceptor(String userAgentHeaderValue) {
this.userAgentHeaderValue = userAgentHeaderValue;
}
@Override public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
.removeHeader(USER_AGENT_HEADER_NAME) //移除先前默认的User-Agent
.addHeader(USER_AGENT_HEADER_NAME, userAgentHeaderValue) //设置新的User-Agent
.build();
return chain.proceed(requestWithUserAgent);
}
}
下面将上面的OkHttpClient设置进Retrofit里:
public enum RetrofitClient {
INSTANCE;
private final Retrofit retrofit;
RetrofitClient() {
retrofit = new Retrofit.Builder()
//设置OKHttpClient
.client(OkHttpFactory.getOkHttpClient())
//baseUrl
.baseUrl(ApiContants.GITHUB_BASEURL)
//gson转化器
.addConverterFactory(GsonConverterFactory.create())
//创建
.build();
}
public Retrofit getRetrofit() {
return retrofit;
}
}
这里使用了枚举单例实现了Retorfit的一个Client,后续就可以轻松使用它了。如下面使用:
public interface GitHubAPI {
@GET("user")
Call<List<User>> getUser();
@GET("users/{user}")
Call<User> getUserInfo(@Path("user") String user);
}
public enum ApiFactory {
INSTANCE;
private static GitHubAPI gitHubAPI;
ApiFactory() {
}
public static GitHubAPI gitHubAPI() {
if (gitHubAPI == null) {
gitHubAPI = RetrofitClient.INSTANCE.getRetrofit().create(GitHubAPI.class);
}
return gitHubAPI;
}
}
最后使用创建ApiFactory类管理所有的API interface,对外提供方法获取他们,这样调用时会方便很多,而且也便于修改。调用如下:
Call<User> userCall = ApiFactory.gitHubAPI().getUserInfo("nickyangjun");
userCall.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
Log.i(TAG, "name: " + response.body().login + " id: " + response.body().id);
mTextView.setText("name: " + response.body().login + " id: " + response.body().id);
}
@Override
public void onFailure(Call<User> call, Throwable t) {
}
});
代码github 地址:https://github.com/nickyangjun/RetrofitTest
未完待续,下节将会从源码角度分析Retrofit。