Retrofit 使用 一

前言

时至今日,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
资源表现层状态转化

每一个URI代表一种资源,客户端和服务器之间,传递这种资源的某种表现层(“资源”具体呈现出来的形式,比如.txt,.png,.jpg)。 客户端通过四个HTTP动作(GET用来获取资源,POST用来新建或更新资源,PUT用来更新资源,DELETE用来删除资源)对服务器端资源进行操作,实现”表现层状态转化”。

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种内置的注解:GETPOSTPUTDELETEHEAD,在注解中指定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的请求注解还有许多更强大的功能,比如:

@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 @Header

请求头的设置可以通过 @Header 注解添加,又有两种添加方式:

设置静态的请求头。

    @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的 Interceptor 。


配置OkHttpClient


由于Retrofit 2.0 底层强制依赖okHttp,所以可以使用okHttp的拦截器Interceptors 来对所有请求进行再处理。目前使用中,一般用来设置UA设置缓存策略 、打印Log 等。这里介绍下配置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。



更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公共号:  Android老鸟

                                                




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值