Retrofit详解

Retrofit详解

在目前的开发环境下,相信Retrofit这个词大家已经非常熟悉了,就像之前volley刚出来的时候大家都一起去使用volley,研究volley源码,进行再次封装使用到自己的项目中;那我也不例外,在之前封装过volley网络框架的基础下也来研究研究retrofit到底有什么比较独特的之处,不过大家要知道retrofit是对okhttp再做了一层封装,你只需要简单的进行一些配置就能顺利使用retrofit来进行网络请求了。

当研究一个框架的时候建议大家从官网和源码下手,虽然目前已经有了很多的文章讲解如何使用Retrofit以及它的源码分析,但是毕竟只有经历过自己研究后总结写出来的东西才属于自己真正的知识,也方便以后去复习和回顾。

Retrofit参考地址:

Retrofit官网介绍

Retrofit源码下载和示例代码

本文中涉及到的demo代码都在Github上:
https://github.com/zphuanlove/RetrofitDemo

1.Retrofit的基本使用

从官网可以看到,Retrofit给我们示例了如何简单的请求网络以及得到结果对象;那么我也利用github提供的api接口来写了一个小demo来给大家演示如何快速使用Retrofit进行网络请求。下面给大家看一个一般的get请求网络方式。

1)首先定义一个接口对象

public interface GitHubService {
    @GET("users/{user}")
    Call<User> getUser(@Path("user") String user);
}

GitHubService接口中有一个getUser的方法,接收一个String类型的参数user用户名。该方法通过注解标明为Get请求方式。

User对象就是一个javaBean对象,里面有一些用户的参数信息,由于字段较多就不在这贴出,具体可以参考示例demo

2)通过Retrofit得到GitHubService对象

Retrofit retrofit = new Retrofit.Builder().
                baseUrl("https://api.github.com/").
                addConverterFactory(GsonConverterFactory.create()).
                build();
        GitHubService service = retrofit.create(GitHubService.class);
        final Call<User> mTestUser = service.getUser("zphuanlove");

这里指定了基本的url,配合着GET注解中的value值形成一个完整的请求路径。

注意上诉代码是在MainActivity主线程中执行

3)同步请求
由于是在主线程中请求网络,同步请求的方式需要new一个子线程来请求:

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            //同步的方式
            Response<User> response = mTestUser.execute();
            User user = response.body();
            Log.i("zph","user:"+user.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}).start();

4)异步的请求

//异步的方式
mTestUser.enqueue(new Callback<User>() {
    @Override
    public void onResponse(Call<User> call, Response<User> response) {
        User body = response.body();
        Log.i("zph","body:"+body.toString());
    }

    @Override
    public void onFailure(Call<User> call, Throwable t) {
        Log.e("zph","error:"+t.toString());
    }
});

通过上诉的代码我们就可以快速的完成一个GET方式的请求,而且Retrofit的源码中提供了有22种注解的方式来供我们请求网络以及配置请求方式,所以如果你还不是很了解注解是如何使用以及配置请参考我的上篇文章:Annotation详解

2.注解的使用和分析

1)Http请求方式

在Retrofit请求Http的方式中一共提供了GET,POST,PUT,PATCH,DELETE,HEAD这几种标准的HTTP请求方法,我们可以看看GET和POST注解的源码:

*GET

/** Make a GET request. */
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
  /**
   * A relative or absolute path, or full URL of the endpoint. This value is optional if the first
   * parameter of the method is annotated with {@link Url @Url}.
   * <p>
   * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how
   * this is resolved against a base URL to create the full endpoint URL.
   */
  String value() default "";
}

*POST

/** Make a POST request. */
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface POST {
  /**
   * A relative or absolute path, or full URL of the endpoint. This value is optional if the first
   * parameter of the method is annotated with {@link Url @Url}.
   * <p>
   * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how
   * this is resolved against a base URL to create the full endpoint URL.
   */
  String value() default "";
}

其他几个注解也都是类似,可以看到这些注解都是作用于方法上,并且是在运行期有效,JVM在运行期通过反射获得注解信息,也就是定义的时候的value值(默认是“”),一般用于动态设置具体的接口请求path,配合BaseUrl组成一个完整的Url,所以如何动态传递path,Retrofit也给我们提供了一个注解Path,看看他的源码:

*PATH

@Documented
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface Path {
  String value();

  /**
   * Specifies whether the argument value to the annotated method parameter is already URL encoded.
   */
  boolean encoded() default false;
}

运行时有效,并且作用于参数上,结合着我们上面的Retrofit基本使用大家应该就明白了这个示例代码的原理了。

不过除了使用上诉的基本HTTP请求方式以外,Retrofit还给我们提供了一种HTTP注解的方式可以自己配置请求方式和path,可以看到下面的代码我使用了HTTP注解的方式同样也可以达到GET请求方式的效果:

public interface GitHubService {
    @GET("users/{user}")
    Call<User> getUser(@Path("user") String user);

    @HTTP(method = "GET",path = "users/{user}",hasBody = false)
    Call<User> getUser4Http(@Path("user") String user);
}

注意这里的method值尽量用大写,如果是post请求hasBody改为true。

*HTTP

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface HTTP {
  String method();
  /**
   * A relative or absolute path, or full URL of the endpoint. This value is optional if the first
   * parameter of the method is annotated with {@link Url @Url}.
   * <p>
   * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how
   * this is resolved against a base URL to create the full endpoint URL.
   */
  String path() default "";
  boolean hasBody() default false;
}

2)请求参数的不同配置

我们都知道在请求的过程中我们是可以携带请求参数给服务器的,Get请求是直接拼接在Url后面,而post请求是放在body中;下面我们就开始介绍下Query,QueryMap,Body,Filed,Part,Head一些注解的使用说明。

  • Query和QueyMap

    Query和QueyMap是作用于请求Url上的参数表现形式,比如我们要请求一个GitHub上的某一个用户的关注用户,具体URl路径是:

http://baseurl/users/{user}/following?sort=id

那么就可以通过PATH和QUERY注解的方式来表达,具体代码如下,定义请求方法:

/**
 * 得到某个用户在github上关注的用户
 * @param user:用户名
 * @param sort:参数sort  需要在调用的时候赋值
 * @return
 */
@GET("users/{user}/following")
Call<List<User>> getUserFollowings(@Path("user") String user, @Query("sort") String sort);

具体请求代码如下:

@Test
public void testFollowing() throws Exception{
    Retrofit retrofit = new Retrofit.Builder().
            baseUrl(GitHubService.BASE_URL).
            addConverterFactory(GsonConverterFactory.create()).
            build();
    GitHubService service = retrofit.create(GitHubService.class);
    Call<List<User>> userFollowings = service.getUserFollowings("zphuanlove", "id");
    Response<List<User>> response = userFollowings.execute();
    List<User> users = response.body();
    System.out.println("users:"+users.toString());
}

同样的如果请求需要带多个参数,那么可以使用QueryMap,比如:

/**
 * 得到某个公司下得所有用户
 * @return
 */
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);
  • Body
    Body的使用主要用于POST请求,携带参数,使用跟一般请求网络框架没什么差别,比如我想在GitHub上创建一个用户,那么需要携带一个User对象过去,这时使用Post提交就非常方便了:
/**
 * 在GitHub上创建用户
 * @param user
 * @return
 */
@POST("users/new")
Call<User> createUser(@Body User user);

具体请求代码:

@Test
public void testCreatUser() throws Exception{
    Retrofit retrofit = new Retrofit.Builder().
            baseUrl(GitHubService.BASE_URL).
            addConverterFactory(GsonConverterFactory.create()).
            build();
    GitHubService service = retrofit.create(GitHubService.class);
    //由于user字段太多就不演示,直接new一个空对象
    Call<User> user = service.createUser(new User());
    //返回也是一个空对象
    User body = user.execute().body();
    System.out.println("user:"+body);
}
  • Filed和FiledMap

    Filed和FiledMap主要是与FormUrlEncoded注解配合,提交表单的键值对信息,作用于参数上,在运行时解析。

    Field源码定义:

@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Field {
  String value();

  /** Specifies whether the {@linkplain #value() name} and value are already URL encoded. */
  boolean encoded() default false;
}

比如我想以表单的方式提交修改用户名:

/**
 * 以表单的方式提交 POST提交 修改用户名
 * @return
 */
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);

具体代码调用:

@Test
public void testFormSubmit() throws Exception{
    Retrofit retrofit = new Retrofit.Builder().
            baseUrl(GitHubService.BASE_URL).
            addConverterFactory(GsonConverterFactory.create()).
            build();
    GitHubService service = retrofit.create(GitHubService.class);
    //这里也是做一个模拟演示
    Call<User> user = service.updateUser("Jack","Lucy");
    //返回也是一个空对象
    User body = user.execute().body();
    System.out.println("user:"+body);
}

FieldMap就是需要携带多个键值对的时候通过map集合传递,Map<String,String>,如果是非String类型会调用其toString方法。

  • Part和PartMap

    Part和PartMap与注解Multipart配合使用,用于文件的上传,如果是单文件上传使用Part即可,如果是多文件上传使用PartMap;作用于参数上,并且在运行时解析。

    示例代码:

/**
 * 单文件上传
 * @param photo
 * @param description
 * @return
 */
@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);

具体代码调用:

@Test
public void testMultipart() throws Exception{
    Retrofit retrofit = new Retrofit.Builder().
            baseUrl(GitHubService.BASE_URL).
            addConverterFactory(GsonConverterFactory.create()).
            build();
    GitHubService service = retrofit.create(GitHubService.class);
    File file = new File("app/src/ic_launcher.png");
    //以数据流的形式
    MediaType mediaType = MediaType.parse("application/octet-stream");
    RequestBody photo = RequestBody.create(mediaType,file);
    //以文件的方式
    RequestBody description = RequestBody.create(MediaType.parse("text/plain"), "description");
    Call<User> updateUser = service.updateUser(photo, description);
    Response<User> response = updateUser.execute();
    System.out.println("updateUser:"+response.body());
    }
  • Header,HeaderMap和Headers
    Header和Headers用于添加请求头信息,不过需要注意的是,Header和HeaderMap,是作用于参数上,用于不确定的header值,而headers是作用于方法体上,用于添加固定的值。

    示例代码:

@Headers({
        "Accept: application/vnd.github.v3.full+json",
        "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser2(@Path("username") String username);

/**
 * 添加不固定的请求头信息
 * @param username
 * @return
 */
@GET("users/{username}")
Call<User> getUser3(@Header("Accept-Language") String lang,@Path("username") String username);

以上就是绝大多数Retrofit下的注解的使用说明了,还有个别的没有说明的也在此给大家简单介绍下,比如

Streaming : 作用于方法体上,运行时解析,当标明该注解的时候标明服务器返回的形式是okhttp3.Response Response,也就是流的形式返回,并不会转换成okhttp3.Response#body() body()} to {@code byte[]},也就是在内存中不会解析数据占用内存,当返回的数据比较大的时候,可以使用该注解。

URL:作用于参数上,运行时解析;该注解表示使用一个完整的url路径来进行请求,也就是说当你的baseUrl变化了的时候,可以直接使用url注解的形式来请求;

3.自定义Converter

Retrofit在数据解析这块也是封装的非常完美,大家可以看到上诉我写的案例中服务器返回的都是json,我也使用的是Google提供给我们的Gson来进行解析,不过并不需要我们自己去解析,Retrofit已经帮我们封装好了,比如上诉的代码:

Retrofit retrofit = new Retrofit.Builder().
                baseUrl("https://api.github.com/").
                addConverterFactory(GsonConverterFactory.create()).
                build();

addConverterFactory就是添加一个解析器工厂:Converter.Factory factory,这里解析json数据直接使用GsonConvertFactory即可;这里Retrofit给我们提供好了几个默认的实现的工厂解析类,如下:

  • 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 XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

一般在移动端应用开发中使用XML和JSON做数据的载体;不过也有特殊的情况需要自己定义一个Converter转换器来实现;比如这里我用一个简单的自定义Converter来做演示,比如我想将返回的数据转化为String,也就是返回的是一个Call<String>该如何下手呢?

首先我们来看看Converter的源码:

public interface Converter<F, T> {
  T convert(F value) throws IOException;

  /** Creates {@link Converter} instances based on a type and target usage. */
  abstract class Factory {
    /**
     * Returns a {@link Converter} for converting an HTTP response body to {@code type}, or null if
     * {@code type} cannot be handled by this factory. This is used to create converters for
     * response types such as {@code SimpleResponse} from a {@code Call<SimpleResponse>}
     * declaration.
     */
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

    /**
     * Returns a {@link Converter} for converting {@code type} to an HTTP request body, or null if
     * {@code type} cannot be handled by this factory. This is used to create converters for types
     * specified by {@link Body @Body}, {@link Part @Part}, and {@link PartMap @PartMap}
     * values.
     */
    public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

    /**
     * Returns a {@link Converter} for converting {@code type} to a {@link String}, or null if
     * {@code type} cannot be handled by this factory. This is used to create converters for types
     * specified by {@link Field @Field}, {@link FieldMap @FieldMap} values,
     * {@link Header @Header}, {@link HeaderMap @HeaderMap}, {@link Path @Path},
     * {@link Query @Query}, and {@link QueryMap @QueryMap} values.
     */
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }
  }

converter类是将返回的F数据类型转换为我们想要的T类型,这里的Factory有三个方法对应具体的解析方法,不过通过注释我们也能看出我们一般只需要重写responseBodyConverter就行了。那么接下来我们就来快速自定义一个converter。

定义StringConverter

代码如下,实现convert方法:

public class StringConverter implements Converter<ResponseBody,String> {

    @Override
    public String convert(ResponseBody value) throws IOException {
        return value.string();
    }
}

定义StringConverterFactory

代码如下,实现responseBodyConverter:

public class StringConverterFactory extends Converter.Factory {

    public static StringConverterFactory create(){
        return new StringConverterFactory();
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        if(type == String.class){
            //如果type是String类型,那么就是用StringConverter去解析
            return new StringConverter();
        }else{
            //其他类型不做处理,返回null
            return null;
        }
    }
}

测试StringConvert

首先在GithubService中定义一个返回String字符串的请求方法:

/**
 * 根据用户名得到当前用户信息
 * @param user:GitHub用户名
 * @return
 */
@GET("users/{user}")
Call<String> getUser4Str(@Path("user") String user);

在单元测试框架中编写一个测试方法进行验证:

@Test
public void testCustomConverter() throws Exception{
    Retrofit retrofit = new Retrofit.Builder().
            baseUrl(GitHubService.BASE_URL).
            addConverterFactory(StringConverterFactory.create()).
            build();
    GitHubService service = retrofit.create(GitHubService.class);
    Call<String> result = service.getUser4Str("zphuanlove");
    String body = result.execute().body();
    System.out.println("返回的结果:"+body);
}

ok,经过验证发现确实能得到想要的字符串结果,证明我们的自定义convert没有任何问题,当然你如果想要自定义其他类型的convert也是可以的,根据具体的需求来做对应的处理即可。

那么Retofit的基本使用以及介绍就到这儿了,希望可以帮助到一些想学习Retofit的童鞋,也可以在下方进行评论和我进行沟通,下次再见。

博客在CSDN中也有发表,地址:http://blog.csdn.net/u013703461

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容