Retrofit2完全教程

御女三千 8年前
   <p>本文中的Retrofit均指代Retrofit2.0。</p>    <p>本文涉及到的代码以及测试使用的接口可在 Github 上找到。</p>    <p>测试接口服务器在 server 项目下,直接运行 RESTServer.main() 即可启动测试服务器,所面代码示例均使用该接口</p>    <p>当然你也可以自己借助 json-server 或 最新开源的Parse 搭建一个REST API,不过都需要安装Node.js,有兴趣的可以去试试。</p>    <p>接口列表:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5f6278402ba466848b8722ea0a6052b7.png"></p>    <p>注:以上的接口的{id}和{page}均代表一个纯数字,/blog/{id} 可以用 /blog?id=XXX 代替,page同理。</p>    <p>前面写了 你应该知道的HTTP基础知识 介绍了HTTP的相关知识,不知道那些想了解Retrofit的同鞋是不是去看了 <a href="/misc/goto?guid=4958837204152834453" rel="nofollow,noindex">Retrofit的官方教程</a> ,曾经我在你真的会用Gson吗? Gson使用指南(四) 中说当你了解了注解、反射、泛型、HTTP的内容只需要看一篇Retrofit的代码示例就可以轻松玩转Retrofit,不知道你玩转了没?</p>    <p>当然注解、反射、泛型的内容还没有写,Retrofit的内容却先来了!毕竟看懂Retrofit也只需要会使就行,你准备好了吗?</p>    <h2><strong>1、Retrofit入门</strong></h2>    <p>Retrofit 其实相当简单,简单到源码只有37个文件,其中22个文件是注解还都和HTTP有关,真正暴露给用户的类并不多,所以我看了一遍 官方教程 大多数情景就可以无障碍使用,如果你还没有看过,可以先去看看,虽然是英文,但代码才是最好的教程不是么?当然本篇文章会介绍得详细一点,不能写一篇水文。</p>    <h3><strong>1.1、创建Retrofit实例</strong></h3>    <pre>  <code class="language-java">Retrofit retrofit = new Retrofit.Builder()          .baseUrl("http://localhost:4567/")          .build();</code></pre>    <p>创建Retrofit实例时需要通过Retrofit.Builder,并调用baseUrl方法设置URL。</p>    <p>注: Retrofit2 的baseUlr 必须以 /(斜线) 结束,不然会抛出一个IllegalArgumentException,所以如果你看到别的教程没有以 / 结束,那么多半是直接从Retrofit 1.X 照搬过来的。</p>    <h3><strong>1.2、接口定义</strong></h3>    <p>以获取指定id的Blog为例:</p>    <pre>  <code class="language-java">public interface BlogService {      @GET("blog/{id}")      Call<ResponseBody> getFirstBlog(@Path("id") int id);  }</code></pre>    <p>注意,这里是interface不是class,所以我们是无法直接调用该方法,我们需要用Retrofit创建一个BlogService的代理对象。</p>    <pre>  <code class="language-java">BlogService service = retrofit.create(BlogService.class);</code></pre>    <p>拿到代理对象之后,就可以调用该方法啦。</p>    <h3><strong>1.3、接口调用</strong></h3>    <pre>  <code class="language-java">Call<ResponseBody> call = service.getFirstBlog(2);  // 用法和OkHttp的call如出一辙,  // 不同的是如果是Android系统回调方法执行在主线程  call.enqueue(new Callback<ResponseBody>() {      @Override      public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {          try {              System.out.println(response.body().string());          } catch (IOException e) {              e.printStackTrace();          }      }        @Override      public void onFailure(Call<ResponseBody> call, Throwable t) {          t.printStackTrace();      }  });</code></pre>    <p>打印结果:</p>    <pre>  <code class="language-java">{    "code": 200,    "msg": "OK",    "data": {      "id": 2,      "date": "2016-04-15 03:17:50",      "author": "怪盗kidou",      "title": "Retrofit2 测试2",      "content": "这里是 Retrofit2 Demo 测试服务器2"    },    "count": 0,    "page": 0  }</code></pre>    <p>示例源码见 <a href="/misc/goto?guid=4959720024315310040" rel="nofollow,noindex">Example01.java</a></p>    <h2><strong>2、Retrofit注解详解</strong></h2>    <p>上面提到Retrofit 共22个注解,这节就专门介绍这22个注解,为帮助大家更好理解我将这22个注解分为三类,并用表格的形式展现出来,表格上说得并不完整,具体的见源码上的例子注释。</p>    <h3><strong>第一类:HTTP请求方法</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/4479b19258a9f74157d0f70f26123104.png"></p>    <p>以上表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:method、path,hasBody,下面是用HTTP注解实现上面 Example01.java 的例子。</p>    <pre>  <code class="language-java">public interface BlogService {      /**       * method 表示请的方法,不区分大小写       * path表示路径       * hasBody表示是否有请求体       */      @HTTP(method = "get", path = "blog/{id}", hasBody = false)      Call<ResponseBody> getFirstBlog(@Path("id") int id);  }</code></pre>    <h3><strong>第二类:标记类</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ed0b78864ef6d5b7a22303c4586e512b.png"></p>    <h3><strong>第三类:参数类</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5b3bd932d7933fefa6053c075a261e55.png"></p>    <p>注1:{占位符}和PATH尽量只用在URL的path部分,url中的参数使用Query和QueryMap 代替,保证接口定义的简洁</p>    <p>注2:Query、Field和Part这三者都支持数组和实现了Iterable接口的类型,如List,Set等,方便向后台传递数组。</p>    <pre>  <code class="language-java">Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);  //结果:ids[]=0&ids[]=1&ids[]=2</code></pre>    <p>Path 示例源码见 Example01.java</p>    <p>Field、FieldMap、Part和PartMap 示例源码见 Example03.java</p>    <p>Header和Headers </p>    <p>Query、QueryMap、Url </p>    <h2><strong>3、Gson与Converter</strong></h2>    <p>在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody,</p>    <p>这也是什么我在前面的例子接口的返回值都是 Call</p>    <p>,</p>    <p>但如果响应体只是支持转换为ResponseBody的话何必要引用泛型呢,</p>    <p>返回值直接用一个Call就行了嘛,既然支持泛型,那说明泛型参数可以是其它类型的,</p>    <p>而Converter就是Retrofit为我们提供用于将ResponseBody转换为我们想要的类型,</p>    <p>有了Converter之后我们就可以写把我们的第一个例子的接口写成这个样子了:</p>    <p> </p>    <pre>  <code class="language-java">public interface BlogService {    @GET("blog/{id}") //这里的{id} 表示是一个变量    Call<Result<Blog>> getFirstBlog(/** 这里的id表示的是上面的{id} */@Path("id") int id);  }</code></pre>    <p>当然只改变泛型的类型是不行的,我们在创建Retrofit时需要明确告知用于将ResponseBody转换我们泛型中的类型时需要使用的Converter</p>    <p>引入Gson支持:</p>    <pre>  <code class="language-java">compile 'com.squareup.retrofit2:converter-gson:2.0.2'</code></pre>    <p>通过GsonConverterFactory为Retrofit添加Gson支持:</p>    <pre>  <code class="language-java">Gson gson = new GsonBuilder()        //配置你的Gson        .setDateFormat("yyyy-MM-dd hh:mm:ss")        .create();    Retrofit retrofit = new Retrofit.Builder()        .baseUrl("http://localhost:4567/")        //可以接收自定义的Gson,当然也可以不传        .addConverterFactory(GsonConverterFactory.create(gson))        .build();</code></pre>    <p>这样Retrofit就会使用Gson将ResponseBody转换我们想要的类型。</p>    <p>这是时候我们终于可以演示如使创建一个Blog了!</p>    <pre>  <code class="language-java">@POST("blog")  Call<Result<Blog>> createBlog(@Body Blog blog);</code></pre>    <p>被@Body注解的的Blog将会被Gson转换成RequestBody发送到服务器。</p>    <pre>  <code class="language-java">BlogService service = retrofit.create(BlogService.class);  Blog blog = new Blog();  blog.content = "新建的Blog";  blog.title = "测试";  blog.author = "怪盗kidou";  Call<Result<Blog>> call = service.createBlog(blog);</code></pre>    <p>结果:</p>    <pre>  <code class="language-java">Result{    code=200,    msg='OK',    data=Blog{      id=20,      date='2016-04-21 05:29:58',      author='怪盗kidou',      title='测试',      content='新建的Blog'    },    count=0,    page=0  }</code></pre>    <h2><strong>4、RxJava与CallAdapter</strong></h2>    <p>说到Retrofit就不得说到另一个火到不行的库RxJava,网上已经不少文章讲如何与Retrofit结合,但这里还是会有一个RxJava的例子,不过这里主要目的是介绍使用CallAdapter所带来的效果。</p>    <p>第3节介绍的Converter是对于Call 中T的转换,而CallAdapter则可以对Call转换,这样的话Call 中的Call也是可以被替换的,而返回值的类型就决定你后续的处理程序逻辑,同样Retrofit提供了多个CallAdapter,这里以RxJava的为例,用Observable代替Call:</p>    <p>引入RxJava支持:</p>    <pre>  <code class="language-java">compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'</code></pre>    <p>通过RxJavaCallAdapterFactory为Retrofit添加RxJava支持:</p>    <pre>  <code class="language-java">Retrofit retrofit = new Retrofit.Builder()        .baseUrl("http://localhost:4567/")        .addConverterFactory(GsonConverterFactory.create())        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())        .build();</code></pre>    <p>接口设计:</p>    <pre>  <code class="language-java">public interface BlogService {    @POST("/blog")    Observable<Result<List<Blog>>> getBlogs();  }</code></pre>    <p>使用:</p>    <pre>  <code class="language-java">BlogService service = retrofit.create(BlogService.class);  service.getBlogs(1)    .subscribeOn(Schedulers.io())    .subscribe(new Subscriber<Result<List<Blog>>>() {        @Override        public void onCompleted() {          System.out.println("onCompleted");        }          @Override        public void onError(Throwable e) {          System.err.println("onError");        }          @Override        public void onNext(Result<List<Blog>> blogsResult) {          System.out.println(blogsResult);        }    });</code></pre>    <p>结果:</p>    <pre>  <code class="language-java">Result{    code=200,    msg='OK',    data=[      Blog{        id=1,        date='2016-04-15 03:17:50',        author='怪盗kidou',        title='Retrofit2 测试1',        content='这里是 Retrofit2 Demo 测试服务器1'      },      .....    ],    count=20,    page=1  }</code></pre>    <p>「补充」:像上面的这种情况最后我们无法获取到返回的Header和响应码的,如果我们需要这两者,提供两种方案:</p>    <ol>     <li>用Observable >``Observable ,这里的Response指retrofit2.Response</li>     <li>用Observable > 代替Observable ,这里的Result是指retrofit2.adapter.rxjava.Result,这个Result中包含了Response的实例</li>    </ol>    <h2><strong>5、自定义Converter</strong></h2>    <p>本节的内容是教大家实现在一简易的Converter,这里以返回格式为Call 为例。</p>    <p>在此之前先了解一下Converter接口及其作用:</p>    <pre>  <code class="language-java">public interface Converter<F, T> {    // 实现从 F(rom) 到 T(o)的转换    T convert(F value) throws IOException;      // 用于向Retrofit提供相应Converter的工厂    abstract class Factory {      // 这里创建从ResponseBody其它类型的Converter,如果不能处理返回null      // 主要用于对响应体的处理      public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,      Retrofit retrofit) {        return null;      }        // 在这里创建 从自定类型到ResponseBody 的Converter,不能处理就返回null,      // 主要用于对Part、PartMap、Body注解的处理      public Converter<?, RequestBody> requestBodyConverter(Type type,      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {        return null;      }        // 这里用于对Field、FieldMap、Header、Path、Query、QueryMap注解的处理      // Retrfofit对于上面的几个注解默认使用的是调用toString方法      public Converter<?, String> stringConverter(Type type, Annotation[] annotations,      Retrofit retrofit) {        return null;      }      }  }</code></pre>    <p>我们要想从Call 转换为 Call 那么对应的F和T则分别对应ResponseBody和String,我们定义一个StringConverter并实现Converter接口。</p>    <pre>  <code class="language-java">public static class StringConverter implements Converter<ResponseBody, String> {      public static final StringConverter INSTANCE = new StringConverter();      @Override    public String convert(ResponseBody value) throws IOException {      return value.string();    }  }</code></pre>    <p>我们需要一个Fractory来向Retrofit注册StringConverter</p>    <pre>  <code class="language-java">public static class StringConverterFactory extends Converter.Factory {      public static final StringConverterFactory INSTANCE = new StringConverterFactory();      public static StringConverterFactory create() {      return INSTANCE;    }      // 我们只关实现从ResponseBody 到 String 的转换,所以其它方法可不覆盖    @Override    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {      if (type == String.class) {        return StringConverter.INSTANCE;      }      //其它类型我们不处理,返回null就行      return null;    }  }</code></pre>    <p>使用Retrofit.Builder.addConverterFactory向Retrofit注册我们StringConverterFactory:</p>    <pre>  <code class="language-java">Retrofit retrofit = new Retrofit.Builder()        .baseUrl("http://localhost:4567/")        // 如是有Gson这类的Converter 一定要放在其它前面        .addConverterFactory(StringConverterFactory.create())        .addConverterFactory(GsonConverterFactory.create())        .build();</code></pre>    <p>注:addConverterFactory是有先后顺序的,如果有多个ConverterFactory都支持同一种类型,那么就是只有第一个才会被使用,而GsonConverterFactory是不判断是否支持的,所以这里交换了顺序还会有一个异常抛出,原因是类型不匹配。</p>    <p>只要返回值类型的泛型参数就会由我们的StringConverter处理,不管是Call 还是Observable</p>    <p>有没有很简单?如果你有其它的需求处理的就自己实现吧。</p>    <p>示例源码见 <a href="/misc/goto?guid=4959720024406492187" rel="nofollow,noindex">Example09.java</a></p>    <h2><strong>6、自定义CallAdapter</strong></h2>    <p>本节将介绍如何自定一个CallAdapter,并验证是否所有的String都会使用我们第5节中自定义的Converter。</p>    <p>先看一下CallAdapter接口定义及各方法的作用:</p>    <pre>  <code class="language-java">public interface CallAdapter<T> {      // 直正数据的类型 如Call<T> 中的 T    // 这个 T 会作为Converter.Factory.responseBodyConverter 的第一个参数    // 可以参照上面的自定义Converter    Type responseType();      <R> T adapt(Call<R> call);      // 用于向Retrofit提供CallAdapter的工厂类    abstract class Factory {      // 在这个方法中判断是否是我们支持的类型,returnType 即Call<Requestbody>和`Observable<Requestbody>`      // RxJavaCallAdapterFactory 就是判断returnType是不是Observable<?> 类型      // 不支持时返回null      public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations,      Retrofit retrofit);        // 用于获取泛型的参数 如 Call<Requestbody> 中 Requestbody      protected static Type getParameterUpperBound(int index, ParameterizedType type) {        return Utils.getParameterUpperBound(index, type);      }        // 用于获取泛型的原始类型 如 Call<Requestbody> 中的 Call      // 上面的get方法需要使用该方法。      protected static Class<?> getRawType(Type type) {        return Utils.getRawType(type);      }    }  }</code></pre>    <p>了解了CallAdapter的结构和其作用之后,我们就可以开始自定义我们的CallAdapter了,本节以CustomCall 为例。</p>    <p>在此我们需要定义一个CustomCall,不过这里的CustomCall作为演示只是对Call的一个包装,并没有实际的用途。</p>    <pre>  <code class="language-java">public static class CustomCall<R> {      public final Call<R> call;      public CustomCall(Call<R> call) {      this.call = call;    }      public R get() throws IOException {      return call.execute().body();    }  }</code></pre>    <p>有了CustomCall,我们还需要一个CustomCallAdapter来实现 Call 到 CustomCall 的转换,这里需要注意的是最后的泛型,是我们要返回的类型。</p>    <pre>  <code class="language-java">public static class CustomCallAdapter implements CallAdapter<CustomCall<?>> {      private final Type responseType;      // 下面的 responseType 方法需要数据的类型    CustomCallAdapter(Type responseType) {      this.responseType = responseType;    }      @Override    public Type responseType() {      return responseType;    }      @Override    public <R> CustomCall<R> adapt(Call<R> call) {      // 由 CustomCall 决定如何使用      return new CustomCall<>(call);    }  }</code></pre>    <p>提供一个CustomCallAdapterFactory用于向Retrofit提供CustomCallAdapter:</p>    <pre>  <code class="language-java">public static class CustomCallAdapterFactory extends CallAdapter.Factory {    public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory();      @Override    public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {      // 获取原始类型      Class<?> rawType = getRawType(returnType);      // 返回值必须是CustomCall并且带有泛型      if (rawType == CustomCall.class && returnType instanceof ParameterizedType) {        Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);        return new CustomCallAdapter(callReturnType);      }      return null;    }  }</code></pre>    <p>使用addCallAdapterFactory向Retrofit注册CustomCallAdapterFactory</p>    <pre>  <code class="language-java">Retrofit retrofit = new Retrofit.Builder()        .baseUrl("http://localhost:4567/")        .addConverterFactory(Example09.StringConverterFactory.create())        .addConverterFactory(GsonConverterFactory.create())        .addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)        .build();</code></pre>    <p>注: addCallAdapterFactory与addConverterFactory同理,也有先后顺序。</p>    <p>示例源码见 <a href="/misc/goto?guid=4959720024493044595" rel="nofollow,noindex">Example10.java</a></p>    <h2><strong>7、其它说明</strong></h2>    <h3><strong>7.1 Retrofit.Builder</strong></h3>    <p>前面用到了 Retrofit.Builder 中的baseUrl、addCallAdapterFactory、addConverterFactory、build方法,还有callbackExecutor、callFactory、client、validateEagerly这四个方法没有用到,这里简单的介绍一下。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d75150e4f80f68477e80dfd4ce339cd7.png"></p>    <h3><strong>7.2 Retrofit的Url组合规则</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6c77b25cfc06406fa5593619490acd17.png"></p>    <p>从上面不能难看出以下规则:</p>    <ul>     <li> <p>如果你在注解中提供的url是完整的url,则url将作为请求的url。</p> </li>     <li> <p>如果你在注解中提供的url是不完整的url,且不以 / 开头,则请求的url为baseUrl+注解中提供的值</p> </li>     <li> <p>如果你在注解中提供的url是不完整的url,且以 / 开头,则请求的url为baseUrl的主机部分+注解中提供的值</p> </li>    </ul>    <h3><strong>7.3 Retrofit提供的Converter</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/959b1fd0ea5eaca7d8ec42d8661799e9.png"></p>    <h3><strong>7.4 Retrofit提供的CallAdapter:</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/74dd3d8bf327f876b2d06e5700c9a76b.png"></p>    <h3> </h3>    <p> </p>    <p>来自:http://ocnyang.com/2016/10/10/Retrofit2/</p>    <p> </p>