Android Retrofit使用指南

TristaLomax 3年前
   <p><img src="https://simg.open-open.com/show/a1126679cf313518dc31e0cb9b088acc.jpg" alt="Android Retrofit使用指南" width="846" height="256"></p>    <p>Retrofit简介</p>    <p><code>Retrofit</code>是大名鼎鼎的 <em>Square</em> 公司开源的适用于<code>Android</code>与<code>Java</code>的网络请求库,官方的介绍非常简短</p>    <pre>  <code class="language-java">A type-safe HTTP client for Android and Java</code></pre>    <p><code>Retrofit</code>使用注解,能够极大的简化网络请求,在2.0版本默认使用<em>Square</em>自家的<code>OkHttp</code>作为底层<code>Http Client</code>,关于如何使用<code>OkHttp</code>配合<code>Retrofit</code>本文后面也会讲到。首先在<code>build.gradle</code>中加入</p>    <pre>  <code class="language-java">  compile 'com.squareup.retrofit2:retrofit:2.0.2'</code></pre>    <p>定义一个网络请求:</p>    <pre>  <code class="language-java">public interface ZhiHuApi {  @GET("users")  Call<List<Repo>> listRepos(@Path("user") String user);  }</code></pre>    <p><code>Retrofit</code>将网络请求转变成了<code>Java interface</code>的形式,<code>interface</code>要获得实例调用<code>listRepos(String user)</code>,需要借助<code>Retrofit.java</code>这个类,通过<code>Retrofit.Builder</code>来配置<code>Retrofit</code>,再通过<code>retrofit.create(final Class<T> service)</code>获取接口的实例</p>    <pre>  <code class="language-java">class Factory {      public static ZhiHuApi create() {      Retrofit retrofit = new Retrofit.Builder()                      .baseUrl("http://news-at.zhihu.com/")                      .build();              return retrofit.create(ZhiHuApi.class);          }  }</code></pre>    <p><code>@GET</code>注解表示请求方式为<code>GET</code>,其中的字符串表示相对<code>URL</code>,而<code>scheme host port</code>被认为是<code>BaseUrl</code>设置到<code>Retrofit</code>中,注意<code>BaseUrl</code>需要以<code>/</code>结尾,这样每个接口定义的相对URL就不需要以<code>/</code>开始。如果在接口中定义的<code>URL</code>为全路径,将用这个全路径作为请求<code>URL</code>,<code>BaseUrl</code>将不起作用。<code>@Path</code>标识get请求的请求参数,上面的<code>listRepos</code>可以认为是在请求<code>http://news-at.zhihu.com/users?user=user</code><br> <code>@POST</code>注解表示请求方式为<code>POST</code>,通常和<code>@FormUrlEncoded</code>注解一起使用,在使用<code>@FormUrlEncoded</code>时可以使用<code>@Field</code>标识表单字段</p>    <pre>  <code class="language-java">@FormUrlEncoded  @POST("user/login.do")  Call<User> login(@Field("username") String userName, @Field("password") String password);</code></pre>    <p>或者使用<code>@FieldMap</code>提交整个<code>map</code></p>    <pre>  <code class="language-java">@FormUrlEncoded  @POST("user/login.do")  Call<User> login(@FieldMap Map<String, String> formMap);</code></pre>    <p>当然你也可以把整个表单封装为一个实体,使用<code>@Body</code>一次提交</p>    <pre>  <code class="language-java">@FormUrlEncoded  @POST("user/login.do")  Call<User> login(@Body User user);</code></pre>    <p><code>Multipart</code>请求时使用<code>@Multipart</code>注解,用<code>@Part</code>标识每个<code>RequestBody</code></p>    <pre>  <code class="language-java">@Multipart  @PUT("user/photo")  Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);</code></pre>    <p>定义好请求之后就可以调用<code>call.enqueue()</code>来执行请求,需要传入<code>Callback<T></code>,其中<code>T</code>的类型编译器会根据<code>Call<T></code>中的类型来判断,<code>Retrofit</code>和其他网络请求库一样对于<em>Android</em>平台做了线程切换,请求在后台执行,<code>Callback<T></code>会回到<code>main (UI) thread</code>,如果是<code>Java</code>程序<code>Callback<T></code>会继续回到调用它的线程。</p>    <pre>  <code class="language-java">ZhiHuApi zhiHuApi = BaseNetwork.Factory.create(ZhiHuApi.class);          Call<User> call = zhiHuApi.login("username", "pwd");          call.enqueue(new Callback<User>() {              @Override              public void onResponse(Call<User> call, Response<User> response) {                }                @Override              public void onFailure(Call<User> call, Throwable t) {                }          });</code></pre>    <p>为请求添加请求头时使用<code>@Headers</code>,这里就不做举例,因为app中通常是每个请求都需要携带请求头,不建议在<code>Retrofit</code>定义请求时传入,而是使用<code>OkHttp</code>来实现统一请求头。</p>    <h2>Converter</h2>    <p><code>Retrofit</code>在默认情况下只能将<code>Http</code>的响应体反序列化到<code>OkHttp</code>的<code>ResponseBody</code>中,加入<code>Converter</code>可以将返回的数据直接格式化成你需要的样子,现有6个<code>Converter</code>可以直接使用:</p>    <ul>     <li>Gson: com.squareup.retrofit2:converter-gson</li>     <li>Jackson: com.squareup.retrofit2:converter-jackson</li>     <li>Moshi: com.squareup.retrofit2:converter-moshi</li>     <li>Protobuf: com.squareup.retrofit2:converter-protobuf</li>     <li>Wire: com.squareup.retrofit2:converter-wire</li>     <li>Simple XML: com.squareup.retrofit2:converter-simplexml</li>     <li>Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars</li>    </ul>    <p>之后在代码里加入(此处以<code>GsonConverterFactory</code>为例)</p>    <pre>  <code class="language-java">  Retrofit retrofit =new Retrofit.Builder()                      .baseUrl(Constants.BASE_HTTP_URL)                      .addConverterFactory(GsonConverterFactory.create())                      .build();</code></pre>    <p>返回的数据会使用<code>Gson</code>解析为对应传入的实体类,你也可以自定义<code>Converter</code>来实现更复杂的需求,只需要<code>extends Converter.Factory</code>然后重写</p>    <pre>  <code class="language-java">  @Override    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,        Retrofit retrofit) {          //your own implements    }      @Override    public Converter<?, RequestBody> requestBodyConverter(Type type,       Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {         //your own implements    }</code></pre>    <p><code>Retrofit</code>终归只是应用层的<code>api</code>,真正的执行器是<code>OkHttp</code>,较为复杂的需求都需要从执行层入手,可以做到<code>Retrofit</code>对外不变的多种自定义统一封装。</p>    <h2>OkHttp配合Retrofit使用</h2>    <p>前文已经提到在<code>Retrofit 2.0</code>中已经默认使用<code>OkHttp</code>作为网络请求执行器,关于<code>OkHttp</code>的优点简单提一下:(<a href="/misc/goto?guid=4959673778567258052">原文链接</a>)</p>    <ul>     <li>1.支持HTTP2/SPDY黑科技</li>     <li>2.socket自动选择最好路线,并支持自动重连</li>     <li>3.拥有自动维护的socket连接池,减少握手次数</li>     <li>4.拥有队列线程池,轻松写并发</li>     <li>5.拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)</li>     <li>6.基于Headers的缓存策略</li>    </ul>    <p>想要使用<code>OkHttp</code>为<code>Retrofit</code>提供更高的定制性,给<code>Retrofit</code>设置自定义的<code>OkHttpClient</code>就可以了</p>    <pre>  <code class="language-java">Retrofit retrofit = new Retrofit.Builder()                    .baseUrl(Constants.BASE_HTTP_URL)                    .client(client)                    .build();</code></pre>    <p>之后就是构建一个<code>OkHttpClient</code></p>    <pre>  <code class="language-java">OkHttpClient client = new OkHttpClient.Builder()             // 向Request Header添加一些业务相关数据,如APP版本,token             .addInterceptor(new HeadInterceptor())             //日志Interceptor,可以打印日志             .addInterceptor(logging)             // 连接超时时间设置             .connectTimeout(10, TimeUnit.SECONDS)             // 读取超时时间设置             .readTimeout(10, TimeUnit.SECONDS)             // 失败重试             .retryOnConnectionFailure(true)             // 支持Https需要加入SSLSocketFactory             .sslSocketFactory(sslSocketFactory)             // 信任的主机名,返回true表示信任,可以根据主机名和SSLSession判断主机是否信任             .hostnameVerifier(new HostnameVerifier() {                 @Override                 public boolean verify(String hostname, SSLSession session) {                     return true;                 }             })             // 使用host name作为cookie保存的key             .cookieJar(new CookieJar() {                 private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();                   @Override                 public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {                     cookieStore.put(HttpUrl.parse(url.host()), cookies);                 }                   @Override                 public List<Cookie> loadForRequest(HttpUrl url) {                     List<Cookie> cookies = cookieStore.get(HttpUrl.parse(url.host()));                     return cookies != null ? cookies : new ArrayList<Cookie>();                 }             })             .build();</code></pre>    <p>如果设置了<code>sslSocketFactory</code>却没有配置对应的<code>hostnameVerifier</code>,那么Https请求是无法成功的。上面用到两个<code>Interceptor</code>分别是<code>HeadInterceptor</code>和<code>HttpLoggingInterceptor</code>,分别是用来添加请求头和打印请求日志的拦截器,<code>OkHttp</code>支持自定义拦截器,例如下面代码自定义的<code>HeadInterceptor</code>为请求加入<code>Headers</code></p>    <pre>  <code class="language-java">public class HeadInterceptor implements Interceptor {        @Override      public Response intercept(Chain chain) throws IOException {          Request originalRequest = chain.request();          Request compressedRequest = originalRequest.newBuilder()                  .headers(Headers.of(getHeaders()))                  .build();          return chain.proceed(compressedRequest);      }  }</code></pre>    <p>有时服务器会对<code>POST</code>提交的表单做参数校验,一种方式是在请求头里加入特定方式加密过的表单参数的<code>Map</code>,那么就需要先获取到请求的<code>Map</code>,通过<code>FormBody</code>可以实现</p>    <pre>  <code class="language-java">// if the server needs to verify post params, use this to get post params;    RequestBody oidBody = originalRequest.body();    Map<String, String> params = new HashMap<>();    if (oidBody instanceof FormBody) {      FormBody formBody = (FormBody) oidBody;      for (int i = 0; i < formBody.size(); i++) {          params.put(formBody.encodedName(i), formBody.encodedValue(i));      }    }</code></pre>    <p><code>HttpLoggingInterceptor</code>是_Square_ 提供的请求信息日志打印工具类,如果需要可以在<code>build.gradle</code>中加入</p>    <pre>  <code class="language-java">  compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'</code></pre>    <p>可以根据不同情况配置日志输出的<code>Level</code>:</p>    <ul>     <li><strong>NONE</strong> 不输出日志</li>     <li><strong>BASIC</strong> 只输出请求方式响应码等基本信息</li>     <li><strong>HEADERS</strong> 只输出请求和响应的头部信息</li>     <li><strong>BODY</strong> 输出请求和响应的头部和请求体信息</li>    </ul>    <p>另外如果遇到两个接口有相互依赖关系,必须请求完第一个接口拿到数据后才知道第二个请求的<code>URL</code>,通常我们会定义两个<code>Retrofit</code>,因为<code>Retrofit</code>的<code>BaseUrl</code>的统一配置的,不过现在可以通过实现动态<code>BaseUrl</code>来避免这个问题,先看<code>DynamicBaseUrlInterceptor</code>的代码</p>    <pre>  <code class="language-java">public class DynamicBaseUrlInterceptor implements Interceptor {      private volatile String host;        public void setHost(String host) {          this.host = host;      }        @Override      public Response intercept(Chain chain) throws IOException {          Request originalRequest = chain.request();          if (!TextUtils.isEmpty(host)) {              HttpUrl newUrl = originalRequest.url().newBuilder()                      .host(host)                      .build();              originalRequest = originalRequest.newBuilder()                      .url(newUrl)                      .build();          }            return chain.proceed(originalRequest);      }  }</code></pre>    <p>在<code>BaseUrl</code>改变时只需要<code>setHost()</code>就可以让下次请求的<code>Baseurl</code>改变</p>    <h2>Retrofit 与 RxJava 结合使用</h2>    <p>本节需要对<code>RxJava</code>基本用法有了解,如果不了解可以忽略或者先去熟悉一下<code>RxJava</code>的<a href="/misc/goto?guid=4959631832560174430">wiki</a>,介绍的目的是因为两者结合使用确实很方便,关于<code>RxJava</code>之后会单独写。</p>    <p><code>RxJava</code>是<code>Rx</code>(全称<code>Reactive Extensions</code>)家族中的一员,是最近很火的响应式编程库,官方对于它的解释很简单</p>    <pre>  <code class="language-java">RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.</code></pre>    <p>一个异步的基于事件的观察者序列,可以理解为扩展的观察者模式,在Android中使用<code>RxJava</code>需要引入两个<code>compile</code>,<code>RxAndroid</code>是专为Android平台打造来提供主线程切换等便利的工具项目。</p>    <pre>  <code class="language-java">   compile 'io.reactivex:rxandroid:1.2.0'     // Because RxAndroid releases are few and far between, it is recommended you also     // explicitly depend on RxJava's latest version for bug fixes and new features.     compile 'io.reactivex:rxjava:1.1.5'</code></pre>    <p><code>Retrofit</code>提供了<code>CallAdapterFactory</code>,它是一个知道如何将<code>call</code>实例转换成其他类型的工厂类,目前支持的有:</p>    <ul>     <li>RxJava</li>     <li>Guava</li>     <li>Java8</li>    </ul>    <p>这些和<code>Retrofit</code>本身都是分离的,需要单独引入<code>compile</code>例如</p>    <pre>  <code class="language-java">compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0'</code></pre>    <p>在代码中配置<code>CallAdapterFactory</code></p>    <pre>  <code class="language-java">Retrofit retrofit =new Retrofit.Builder()                     .baseUrl(Constants.BASE_HTTP_URL)                     .addConverterFactory(GsonConverterFactory.create())                     .addCallAdapterFactory(RxJavaCallAdapterFactory.create())                     .build();</code></pre>    <p>之后就可以把请求的返回改为<code>Observable<T></code>了</p>    <pre>  <code class="language-java">@GET("users")  Observable<List<Repo>> listRepos(@Path("user") String user);</code></pre>    <p>请求时只需要</p>    <pre>  <code class="language-java">BaseNetwork.Factory.create(Foo.class)                  .listRepos("user")                  .observeOn(AndroidSchedulers.mainThread())//观察者所在的线程                  .subscribeOn(Schedulers.io())//请求执行的线程                  //如果正常执行会顺序调用onNext,onCompleted,如果出错则会调用onError                  .subscribe(new Observer<List<Repo>>() {                      @Override                      public void onCompleted() {                        }                        @Override                      public void onError(Throwable e) {                        }                        @Override                      public void onNext(List<Repo> list) {                        }                  });</code></pre>    <p>如果项需要服务器返回固定的格式用来定义一些业务上的错误如下</p>    <pre>  <code class="language-java">{    "state":0,//状态码,0为业务正常    "msg":"",//如果业务出错,携带错误信息    "data":{}//包含实际业务实体  }</code></pre>    <p>需要定义统一的响应实体,根据<code>T</code>传入的类型来获取业务实体真实的类型</p>    <pre>  <code class="language-java">public class BaseResult<T> {      private int state;      private String msg;      private T data;        public int getState() {          return state;      }        public void setState(int state) {          this.state = state;      }        public String getMsg() {          return msg;      }        public void setMsg(String msg) {          this.msg = msg;      }        public T getData() {          return data;      }        public void setData(T data) {          this.data = data;      }  }</code></pre>    <p>请求中的泛型类型需要是<code>BaseResult<T></code></p>    <pre>  <code class="language-java">@GET("users")  Observable<BaseResult<List<Repo>>> listRepos(@Path("user") String user);</code></pre>    <p>调用时也会有改变,需要经过一次拆解统一返回,处理错误的过程</p>    <pre>  <code class="language-java">BaseNetwork.Factory.create(Foo.class)                  .listRepos("user")                  .flatMap(new NetworkResultFunc1<List<Repo>>())                  .observeOn(AndroidSchedulers.mainThread())                  .subscribeOn(Schedulers.io())                  .subscribe(observer);</code></pre>    <p><code>flatMap</code>需要传入<code>Func1<T, R></code>,<code>Func1<T, R></code>继承了<code>Function</code>,只有一个方法,将泛型参数列表的第一个转换为第二个返回,它可以将<code>Observable</code>做一个展开,并返回一个新的<code>Observable</code></p>    <pre>  <code class="language-java">public interface Func1<T, R> extends Function {      R call(T t);  }</code></pre>    <p><code>NetworkResultFunc1<List<Repo>></code>实现了<code>Func1<T, R></code>,代码如下</p>    <pre>  <code class="language-java">public class NetworkResultFunc1<T> implements Func1<BaseResult<T>, Observable<T>> {        @Override      public Observable<T> call(final BaseResult<T> tBaseResult) {          return Observable.create(new Observable.OnSubscribe<T>() {              @Override              public void call(Subscriber<? super T> subscriber) {                  int state = tBaseResult.getState();                  String msg = tBaseResult.getMsg();                  switch (state) {                      case 0://if success, return data to client                          subscriber.onNext(tBaseResult.getData());                          break;                      case 1000://if this means error                          subscriber.onError(new ApiException(state, msg));                          break;                  }                  subscriber.onCompleted();//no error, will execute onCompleted()              }          });      }  }</code></pre>    <p>如果state为0,则调用<code>subscriber.onNext()</code>向调用者返回数据,当state不等于0时意味着业务出错了,向<code>subscriber.onError()</code>中抛了一个<code>ApiException</code>,这样在<code>Observer</code>处会回调<code>onError()</code>终止整个事件流,调用者也能获得业务错误的相关信息。<code>ApiException</code>代码如下,就是一个自定义的<code>RuntimeException</code></p>    <pre>  <code class="language-java">public class ApiException extends RuntimeException {      private int state;      private String msg;      public ApiException(int state, String msg) {          this.state = state;          this.msg = msg;      }        public int getState() {          return state;      }        public String getMsg() {          return msg;      }  }</code></pre>    <p>对于<code>Retrofit</code>的介绍就先到这里,相信看到这里,你已经能够在项目中优雅的使用<code>Retrofit</code>了。</p>    <p><br> 文/<a href="/misc/goto?guid=4959673778685339668">winter1991</a>(简书)<br>  </p>    <p> </p>