Retrofit2学习笔记

时至今日,Android的网络框架不再像之前那么到处都是,随着Google把HttpClient直接删掉,似乎意味着Android越来越成熟。网络框架中的佼佼者Volley也不再那么光鲜,取而代之的是Retrofit和okHttp。 这两个网络库都是Square这个牛逼的公司出品的,这个公司出品了好多开源库, 大家有兴趣可以去了解下.

作为一个资深的Android从业者(呵呵~), 一直没有好好研究过retrofit, 这次下定决心要好好研究一下包括Retrofit + okHttp + RxJava等一系列新技术, 免得被时代抛弃.

前言

如果看Retrofit的源码会发现其实质上就是对okHttp的封装,使用面向接口的方式进行网络请求,利用动态生成的代理类封装了网络接口请求的底层, 其将请求返回javaBean. 它非常适合于restful url格式的请求,使用更多的注解方式提供各种功能.

Restful

Retrofit 把REST API返回的数据转化为Java对象,就像ORM框架那样,把数据库内的存储的数据转化为相应的Java bean对象。
那么我们知道Retrofit是一个类型安全的网络框架,而且它是使用REST API的,接下来我们看看什么是REST吧。

以一个简单的例子来入门

引入Retrofit库

  • 首先,在gralde文件中引入后续要用到的库。(以下是我参考的其它博文中的,其中的版本可能有点老,先不管那么多了,能跑起来再说, 等我试验完后把它们更新成最新的版本)

    compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
    compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
    compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
    
    compile 'com.squareup.okhttp:okhttp:2.4.0'
    
    compile 'io.reactivex:rxjava:1.0.14'
    compile 'io.reactivex:rxandroid:1.0.1'
    

接下来我们写一个例子,并一步步进行分析。

小例子

  • 创建两个类: 实体Bean和服务接口类

    public static class Contributor {
        public final String login;
        public final int contributions;
        public Contributor(String login, int contributions) {
            this.login = login;
            this.contributions = contributions;
        }
        @Override
        public String toString() {
            return "Contributor{" +
                "login='" + login + '\'' +
                ", contributions=" + contributions +
                '}';
        }
    }
    
    public interface GitHub {
        @GET("/repos/{owner}/{repo}/contributors")
        Call<List<Contributor>> contributors(
            @Path("owner") String owner,
            @Path("repo") String repo);
    }
    
  • 接下来创建Retrofit2的实例,并设置BaseUrl和Gson转换。
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://api.github.com")
            .addConverterFactory(GsonConverterFactory.create())
            .client(new OkHttpClient())
            .build();
    
  • 创建请求服务,并为网络请求方法设置参数
    GitHub gitHubService = retrofit.create(GitHub.class);
    Call<List<Contributor>> call = gitHubService.contributors("square", "retrofit");
    
  • 最后,请求网络,并获取响应
    try{
        Response<List<Contributor>> response = call.execute(); // 同步
        Log.d(TAG, "response:" + response.body().toString());
    } catch (IOException e) {
        e.printStackTrace();
    }
    
  • Call是Retrofit中重要的一个概念,代表被封装成单个请求/响应的交互行为
    • 通过调用Retrofit2的execute(同步)或者enqueue(异步)方法,发送请求到网络服务器,并返回一个响应(Response)。
    • 它具有如下特点
      • 独立的请求和响应模块
      • 从响应处理分离出请求创建
      • 每个实例只能使用一次。
      • Call可以被克隆。
      • 支持同步和异步方法。
      • 能够被取消。
  • 由于call只能被执行一次,所以按照上面的顺序再次执行Call将会得到如下错误。
    java.lang.IllegalStateException: Already executed
    
  • 我们可以通过clone,来克隆一份call,从而可以重新调用一次这个接口

    // clone
    Call<List<Contributor>> call1 = call.clone();
    // 5. 请求网络,异步
    call1.enqueue(new Callback<List<Contributor>>() {
        @Override
        public void onResponse(Response<List<Contributor>> response, Retrofit retrofit) {
            Log.d(TAG, "response:" + response.body().toString());
        }
    
        @Override
        public void onFailure(Throwable t) {
    
        }
    });
    

总结

下面我们总结一下使用retrofit的基本步骤

  1. 定义网络请求的实体bean和服务接口类
  2. 创建retrofit2的实例
    • 在这里需要设置baseUrl
    • 设置一个OkHttpClient类的实例,可以是默认的,也可以是定制的,如果有需要可以整体app使用一个单例实例. 有空再单独说一下这个事.
    • 设置Gson等转换器
  3. 创建请求服务代理类, 并为网络请求方法设置参数,生成一个Call对象
  4. 请求网络, 并获取响应
    • 支持execute(同步)和 enqueue(异步)
    • Call只能执行一次
    • 能够被取消

retrofit 用法

服务接口类

Retrofit需要注解接口的请求方法和方法的参数来表明该请求需要怎么样的处理。

请求方法

  • 每一个方法必须要有一个HTTP注解来标明请求的方式和相对URL。有五种内置的注解方式:GET、POST、PUT、DELETE以及HEAD。资源的相对URL需要在注解里面明确给出:
    @GET("users/list")
    
  • 当然作为特殊用法: 你也可以将query参数直接写死在URL里
    @GET("users/list?sort=desc")
    

URL操作

包括路径的替换, 参数的替换, 请求头参数, post请求, 表单等

路径替换和参数

  1. 路径替换

    interface SomeService {
     @GET("/some/endpoint/{thing}")
     Call<SomeResponse> someEndpoint(
     @Path("thing") String thing);
    }
    
    someService.someEndpoint("bar");
    
    // GET /some/endpoint/bar HTTP/1.1
    
  2. 固定查询参数

    // 服务
    interface SomeService {
     @GET("/some/endpoint?fixed=query")
     Call<SomeResponse> someEndpoint();
    }
    
    // 方法调用
    someService.someEndpoint();
    
    // 请求头
    // GET /some/endpoint?fixed=query HTTP/1.1
    
  3. 动态参数

    // 服务
    interface SomeService {
     @GET("/some/endpoint")
     Call<SomeResponse> someEndpoint(
     @Query("dynamic") String dynamic);
    }
    
    // 方法调用
    someService.someEndpoint("query");
    
    // 请求头
    // GET /some/endpoint?dynamic=query HTTP/1.1
    
  4. 动态参数(Map)

    // 服务
    interface SomeService {
     @GET("/some/endpoint")
     Call<SomeResponse> someEndpoint(
     @QueryMap Map<String, String> dynamic);
    }
    
    // 方法调用
    someService.someEndpoint(
     Collections.singletonMap("dynamic", "query"));
    // 请求头
    // GET /some/endpoint?dynamic=query HTTP/1.1
    
  5. 省略动态参数

    interface SomeService {
     @GET("/some/endpoint")
     Call<SomeResponse> someEndpoint(
     @Query("dynamic") String dynamic);
    }
    
    // 方法调用
    someService.someEndpoint(null);
    
    // 请求头
    // GET /some/endpoint HTTP/1.1
    
  6. 固定+动态参数

    interface SomeService {
     @GET("/some/endpoint?fixed=query")
     Call<SomeResponse> someEndpoint(
     @Query("dynamic") String dynamic);
    }
    
    // 方法调用
    someService.someEndpoint("query");
    
    // 请求头
    // GET /some/endpoint?fixed=query&dynamic=query HTTP/1.1
    

    请求头

  7. 固定头

    interface SomeService {
     @GET("/some/endpoint")
     @Headers("Accept-Encoding: application/json")
     Call<SomeResponse> someEndpoint();
    }
    
    someService.someEndpoint();
    
    // GET /some/endpoint HTTP/1.1
    // Accept-Encoding: application/json
    
  8. 动态头

    interface SomeService {
     @GET("/some/endpoint")
     Call<SomeResponse> someEndpoint(
     @Header("Location") String location);
    }
    
    someService.someEndpoint("Droidcon NYC 2015");
    
    // GET /some/endpoint HTTP/1.1
    // Location: Droidcon NYC 2015
    
  9. 固定+动态头

    interface SomeService {
        @GET("/some/endpoint")
        @Headers("Accept-Encoding: application/json")
        Call<SomeResponse> someEndpoint(
            @Header("Location") String location);
    }
    
    someService.someEndpoint("Droidcon NYC 2015");
    
    // GET /some/endpoint HTTP/1.1
    // Accept-Encoding: application/json
    // Location: Droidcon NYC 2015
    

    post和put请求

  10. Post请求,无Body

    interface SomeService {
     @POST("/some/endpoint")
     Call<SomeResponse> someEndpoint();
    }
    
    someService.someEndpoint();
    
    // POST /some/endpoint?fixed=query HTTP/1.1
    // Content-Length: 0
    
  11. Post请求有Body

    interface SomeService {
     @POST("/some/endpoint")
     Call<SomeResponse> someEndpoint(
     @Body SomeRequest body);
    }
    
    someService.someEndpoint();
    
    // POST /some/endpoint HTTP/1.1
    // Content-Length: 3
    // Content-Type: greeting
    //
    // Hi!
    
  12. 表单编码字段

    interface SomeService {
     @FormUrlEncoded
     @POST("/some/endpoint")
     Call<SomeResponse> someEndpoint(
         @Field("name1") String name1,
         @Field("name2") String name2);
    }
    
    someService.someEndpoint("value1", "value2");
    
    // POST /some/endpoint HTTP/1.1
    // Content-Length: 25
    // Content-Type: application/x-www-form-urlencoded
    //
    // name1=value1&name2=value2
    
  13. 表单编码字段(Map)

    interface SomeService {
     @FormUrlEncoded
     @POST("/some/endpoint")
     Call<SomeResponse> someEndpoint(
         @FieldMap Map<String, String> names);
    }
    
    someService.someEndpoint(
     // ImmutableMap是OKHttp中的工具类
     ImmutableMap.of("name1", "value1", "name2", "value2"));
    
    // POST /some/endpoint HTTP/1.1
    // Content-Length: 25
    // Content-Type: application/x-www-form-urlencoded
    //
    // name1=value1&name2=value2
    
  14. 可以通过@Multipart注解方法
    来发送Mutipart请求。每个部分需要使用@Part来注解。

    @Multipart
    @PUT("user/photo")
    Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
    
    // 多个请求部分需要使用Retrofit的converter或者是自己实现 RequestBody来处理自己内部的数据序列化。
    

上传和下载

单文件上传@Multipart

多文件上传@PartMap

下载文件

  • 这个其实我觉得直接使用okhttp就好了,使用retrofit去做这个事情真的有点瞎用的感觉~~

Retrofit配置

Retrofit类会通过你定义的API接口转化为可调用的对象。默认情况下,Retrofit会返还给你合理的默认值,但也允许你进行指定。

转化器(Converters)

  • 默认情况下,Retrofit只能将HTTP体反序列化为OKHttp的 ResonseBody 类型,而且只能接收 RequestBody类型作为 @Body。
  • 转化器的加入可以用于支持其他的类型。以下六个同级模块采用了常用的序列化库来为你提供方便。
    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
    
  • 下面提供一个使用GsonConverterFactory类生成 GitHubService的接口实现gson反序列化的例子。

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com")
        .addConverterFactory(GsonConverterFactory.create())
        .build();
    
    GitHubService service = retrofit.create(GitHubService.class);
    
  • 自定义转化器
    • 如果你需要与没有使用Retrofit提供的内容格式的API进行交互的话或者是你希望使用一个不同的库来实现现有的格式,你也可以轻松创建使用自己的转化器。
    • 你需要创建一个继承自Converter.Factory的类并且在构建适配器的时候加入到实例里面。

可插拔的执行机制(Multiple, pluggable execution mechanisms)

interface GitHubService {
 @GET("/repos/{owner}/{repo}/contributors")
 // Call 代表的是CallBack回调机制
 Call<List<Contributor>> repoContributors(
     @Path("owner") String owner,
     @Path("repo") String repo);

 @GET("/repos/{owner}/{repo}/contributors")
 // Observable 代表的是RxJava的执行
 Observable<List<Contributor>> repoContributors2(
     @Path("owner") String owner,
     @Path("repo") String repo);

 @GET("/repos/{owner}/{repo}/contributors")
 Future<List<Contributor>> repoContributors3(
     @Path("owner") String owner,
     @Path("repo") String repo);
}
  • 注意,要在构建Retrofit时指定适配器模式为RxJavaCallAdapterFactory
    Retrofit retrofit = new Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .baseUrl("http://www.duitang.com")
        .build();
    
  • 否则,会报出如下错误:
    Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for rx.Observable<com.bzh.sampleretrofit.ClubBean>. Tried:
    * retrofit.ExecutorCallAdapterFactory
    

配置OkHttpClient

  • 这个需要简单提一下,很多时候,比如你使用retrofit需要统一的log管理,给每个请求添加统一的header等,这些都应该通过okhttpclient去操作,比如addInterceptor
  • 例如:
    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor()//log,统一的header等
    {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException
        {
            return null;
        }
    }).build();
    
  • 或许你需要更多的配置,你可以单独写一个OkhttpClient的单例生成类,在这个里面完成你所需的所有的配置,然后将OkhttpClient实例通过方法公布出来,设置给retrofit。
  • 设置方式:
    Retrofit retrofit = new Retrofit.Builder()
        .callFactory(OkHttpUtils.getClient())
        .build();
    
  • callFactory方法接受一个okhttp3.Call.Factory对象,OkHttpClient即为此Factory类的一个实现类

混淆

如果你的工程中使用了代码混淆,那么你的配置中需要添加一下的几行
-dontwarn retrofit2.
-keep class retrofit2.
{ *; }
-keepattributes Signature
-keepattributes Exceptions

Retrofit2结构

  • Retrofit作为一个上层框架,自然有很多底层lib库支持,okio和okhttp都包含其中。
    1

Retrofit执行模式

  • 2
  • 3
  • 4
  • 5

参考

Retrofit2 完全解析 探索与okhttp之间的关系
Retrofit2.0使用总结
好用的网络请求库Retrofit2(入门及讲解)
Retrofit 2.0非常简单的入门(翻译官方文档)

文章目录
  1. 1. 前言
    1. 1.1. Restful
  2. 2. 以一个简单的例子来入门
    1. 2.1. 引入Retrofit库
    2. 2.2. 小例子
    3. 2.3. 总结
  3. 3. retrofit 用法
    1. 3.1. 服务接口类
      1. 3.1.1. 请求方法
      2. 3.1.2. URL操作
        1. 3.1.2.1. 路径替换和参数
        2. 3.1.2.2. 请求头
        3. 3.1.2.3. post和put请求
    2. 3.2. 上传和下载
      1. 3.2.1. 单文件上传@Multipart
      2. 3.2.2. 多文件上传@PartMap
      3. 3.2.3. 下载文件
    3. 3.3. Retrofit配置
      1. 3.3.1. 转化器(Converters)
      2. 3.3.2. 可插拔的执行机制(Multiple, pluggable execution mechanisms)
      3. 3.3.3. 配置OkHttpClient
    4. 3.4. 混淆
  4. 4. Retrofit2结构
  5. 5. Retrofit执行模式
  6. 6. 参考