Retrofit2.0使用简介

随着Google抛弃HttpClient以及Volley的逐步没落,OkHttp越来越受到开发者的青睐。在之前的博文中《OkHttp源码解析》,我们已经对OkHttp的使用和原理进行了分析。Retrofit作为square公司的另一网络利器,则是在OkHttp的基础上,对网络请求做了更加系统、简洁的封装,本博文就来带大家认识和学习Retrofit,将知识转化为战斗力。
本博文基于Retrofit 2.3.0进行讲解

Retrofit是什么?

按照惯例,我们首先来看下GitHub上Retrofit的解释。
Type-safe HTTP client for Android and Java by Square, Inc.
翻译下:
Square公司为Android和Java开发的类型安全的Http Client。

Retrofit其实就是对OkHttp的封装,使用面向接口的方式进行网络请求(大大简化了我们的代码,感觉这一点和Spring中Controller的接口编写类似),它使用动态生成的代理类封装了接口,而且使用很多注解提供功能。

如何使用Retrofit?

   //Retrofit本体
    compile 'com.squareup.retrofit2:retrofit:2.3.0'
    //OkHttp
    compile 'com.squareup.okhttp3:okhttp:3.9.0'
    //OkHttp依赖的okio
    compile 'com.squareup.okio:okio:1.13.0'
    //Retrofit Gson转换器
    compile 'com.squareup.retrofit2:converter-gson:2.3.0'

忘记告诉你,Retrofit的使用要依赖于OkHttp,所以你还要加入对OkHttp库的依赖(当然别忘了,还要okio)。

当然,如果是Android开发者,不要忘记请求、添加网络权限。

<uses-permission android:name="android.permission.INTERNET"/>

当你做完了如上的动作,我们的武器库就填好了,下面我们就来看看如何使用我们的武器吧。

API

这个章节,我们主要来讨论和学习下如何使用Retrofit。示例都是一些简单的例子,以便我们快速上手Retrofit。

1. 示例

  1. 定义HTTP API接口
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

Retrofit会使用动态代理的方式,将我们定义的Java Interface转换为网络请求接口,我们只需要定义和使用,暂时不关注它是如何进行转换的。

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

从这段代码上看,Retrofit的构造同样使用了设计模式中的[建造者模式],可见该模式的使用之普遍。
在构造Retrofit时我们要注意以下几点:

  • baseUrl以/结尾。
  • @GET等接口URL不要以/开头。
  1. 转换接口
GitHubService service = retrofit.create(GitHubService.class);

Retrofit使用动态代理的方式,实现了我们定义的GitHubService接口。

  1. 调用API接口
GitHubService service = retrofit.create(GitHubService.class);

//同步请求
//https://api.github.com/users/zhangshuaisir/repos
Call<List<Repo>> call = service.listRepos("zhangshuaisir");
try {
     Response<List<Repo>> repos  = call.execute();
} catch (IOException e) {
     e.printStackTrace();
}

//不管同步还是异步请求,call只能执行一次,否则会抛 IllegalStateException
Call<List<Repo>> clone = call.clone();

//异步请求
//Android 回调接口执行在Android主线程中
call.enqueue(new Callback<List<Repo>>() {
        @Override
        public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
            for (Repo repo : response.body()) {
                System.out.println(repo.toString());
            }
        }

        @Override
        public void onFailure(Call<List<Repo>> call, Throwable t) {
            t.printStackTrace();
        }
    });
  1. 终止请求
call.cancel();

即使该请求正在执行,调用该接口仍然可以终止它。

2.Annotation

在上面的示例中,我们看到了[ @GET("users/{user}/repos")]注解,凭借我们的直觉注解代表是GET请求,那么本章节就带大家来认识下Retrofit注解相关内容。

注解的作用:
添加在方法上的注解和参数,代表了Retrofit如何处理这个请求。

  1. 方法注解
    每一个方法都必须包含一个提供请求方法和相对URL的注解,Retrofit内置了五个注解,资源的相对URL在注解中指定。内置的注解有:
  • @GET
  • @POST
  • @PUT
  • @DELETE
  • @HEAD
    这些注解对应了我们请求处理方式,相信大家看完就能理解,在此不再赘述。

2 URL

(1)动态URL

  • 请求URL可以使用方法上的替换块和参数动态更新请求URL。
  • 替换块是由{}包围的字母数字字符串,使用@Path注释相应的参数。
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);

我们可以将{id}理解为占位符,在实际的运行中,该值会被@Path("id")标注的参数替换,从而达到动态更新URL的目的。

@Path注解的定位是替换URL的路径,而不是查询参数替换。

(2)添加查询参数

我们可以通过使用@Query注解来向URL中添加查询参数,示例如下:

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);

在实际的运行中,@Query("sort")注解会被如此解释:https://api.github.com/users/zhangshuaisir/repos?sort=desc.

对于复杂的查询参数构造,可以使用@QueryMap来处理。

@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);

@Query和@QueryMap注解定位于URL中查询参数的替换,与@Path(URL路径替换)的作用和定位不同,使用时要注意。

  1. 请求体
    一个对象可以通过使用@Body注解作为HTTP request body使用。
@POST("users/new")
Call<User> createUser(@Body User user);

实体会根据配置在Retrofit的converter进行转换,如果在构造Retrofit的时候我们没有配置converter,那么只能使用RequestBody类型的实体。

  1. 表单方式传递值
    Retrofit支持传递[form-encoded]数据,使用@FormUrlEncoded注解进行标识。
    每个key-value值,使用@Field进行标识。
    通过@POST指明url,添加FormUrlEncoded,然后通过@Field添加参数即可。
    当我们有很多个表单参数时也可以通过@FieldMap注解和Map对象参数来指定每个表单项的Key,value的值。
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
  1. 文件上传
    Retrofit同样支持Multipart,使用@Mutipart进行注解,Parts使用@Part注解。
@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
  1. Header
    可以使用@Headers标签静态来指定请求的Header。
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();


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

当然我们也可以使用@Header标签动态指定Header。

@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

3. 同步&异步

Call实例可以执行同步和异步请求,但是每个实例只能执行一次,可以使用clone方法创建一个新的示例。

在Android中,callbacks是执行在主线程中的;在JVM中,callbacks在调用请求的线程中执行。

4.Retrofit配置

CONVERTERS

默认情况下,Retrofit只能将HTTP Body实体转换为OkHttp的ResponseBody类型;
而且它也只能接收RequestBody类型的请求实体。

除了默认的转换器,我们还可以添加自定义的转换器,我们常用的主要有以下几种:
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

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

GitHubService service = retrofit.create(GitHubService.class);

如果以上的转换器仍然满足不了你的需求,那么我们可以自定义转换器。
只需要继承Converter.Factory类,我们就可以自定义转换器。
Converter.Factory类是一个抽象类,我们来看下它的定义。

  /** 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 @Nullable 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 @Nullable 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 @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

    /**
     * Extract the upper bound of the generic parameter at {@code index} from {@code type}. For
     * example, index 1 of {@code Map<String, ? extends Runnable>} returns {@code Runnable}.
     */
    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    /**
     * Extract the raw class type from {@code type}. For example, the type representing
     * {@code List<? extends Runnable>} returns {@code List.class}.
     */
    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }

关于自定义Converter,有以下几点我们需要注意:

  • responseBodyConverter:主要完成ResponseBody到实际的返回类型的转化,这个类型对应Call<\XXX>里面的泛型XXX。
  • requestBodyConverter:完成对象到RequestBody的构造。主要是对应@Body注解,其实@Part等注解也会需要requestBodyConverter,只不过我们的参数类型都是RequestBody,由默认的converter处理了。
  • 一定要注意,检查type如果不是自己能处理的类型,记得return null (因为可以添加多个,你不能处理return null ,还会去遍历后面的converter).

Proguard

# Platform calls Class.forName on types which do not exist on Android to determine platform.
-dontnote retrofit2.Platform
# Platform used when running on Java 8 VMs. Will not be used at runtime.
-dontwarn retrofit2.Platform$Java8
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions

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

推荐阅读更多精彩内容