Android 手把手教你使用Retrofit2

lixuqu 8年前
   <h2>Android 手把手教你使用Retrofit2</h2>    <h2>前言:</h2>    <p>1.我想通过本文,让大家能更快的上手Retrofit2,跟上时代的步伐。</p>    <p>2.本文主要是写给初级、中级开发人员阅读,所以在这边不会做太多的解释说明,只针对一些坑以及使用方法进行说明。</p>    <p>3.本文只涉及到Retrofit2,并未与RxJava一起使用,RxJava+Retrofit2技术将会在下篇文章中呈现。</p>    <p>4.本人也是刚开始接触Retrofit2,所以可能文章中会有一些错误或者不足,希望大家多多留言探讨,共同进步</p>    <h2>开启Retrofit2之旅</h2>    <h3>添加依赖</h3>    <p>以下是我用到的一些依赖</p>    <pre>  <code class="language-java">compile 'com.squareup.retrofit2:retrofit:2.1.0'      compile 'com.squareup.retrofit2:converter-gson:2.1.0'      compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'      //日志拦截器      compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'      compile 'io.reactivex:rxjava:1.2.4'      compile 'io.reactivex:rxandroid:1.2.1'      compile 'org.ligboy.retrofit2:converter-fastjson-android:2.1.0'</code></pre>    <h3>注解</h3>    <p>retrofit通过使用注解来简化请求,大体分为以下几类:</p>    <p>1.用于标注请求方式的注解</p>    <p>2.用于标记请求头的注解</p>    <p>3.用于标记请求参数的注解</p>    <p>4.用于标记请求和响应格式的注解</p>    <p>请求方法注解</p>    <table>     <thead>      <tr>       <th>注解</th>       <th>说明</th>      </tr>     </thead>     <tbody>      <tr>       <td>@GET</td>       <td>get请求</td>      </tr>      <tr>       <td>@POST</td>       <td>post请求</td>      </tr>      <tr>       <td>@PUT</td>       <td>put请求</td>      </tr>      <tr>       <td>@DELETE</td>       <td>delete请求</td>      </tr>      <tr>       <td>@PATCH</td>       <td>patch请求,该请求是对put请求的补充,用于更新局部资源</td>      </tr>      <tr>       <td>@HEAD</td>       <td>head请求</td>      </tr>      <tr>       <td>@OPTIONS</td>       <td>option请求</td>      </tr>      <tr>       <td>@HTTP</td>       <td>通用注解,可以替换以上所有的注解,其拥有三个属性:method,path,hasBody</td>      </tr>     </tbody>    </table>    <p>请求头注解</p>    <table>     <thead>      <tr>       <th>注解</th>       <th>说明</th>      </tr>     </thead>     <tbody>      <tr>       <td>@Headers</td>       <td>用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在</td>      </tr>      <tr>       <td>@Header</td>       <td>作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头</td>      </tr>     </tbody>    </table>    <p>请求参数注解</p>    <table>     <thead>      <tr>       <th>名称</th>       <th>说明</th>      </tr>     </thead>     <tbody>      <tr>       <td>@Body</td>       <td>多用于post请求发送非表单数据,比如想要以post方式传递json格式数据</td>      </tr>      <tr>       <td>@Filed</td>       <td>多用于post请求中表单字段,Filed和FieldMap需要FormUrlEncoded结合使用</td>      </tr>      <tr>       <td>@FiledMap</td>       <td>和@Filed作用一致,用于不确定表单参数</td>      </tr>      <tr>       <td>@Part</td>       <td>用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况</td>      </tr>      <tr>       <td>@PartMap</td>       <td>用于表单字段,默认接受的类型是Map<String,REquestBody>,可用于实现多文件上传</td>      </tr>      <tr>       <td>@Path</td>       <td>用于url中的占位符</td>      </tr>      <tr>       <td>@Query</td>       <td>用于Get中指定参数</td>      </tr>      <tr>       <td>@QueryMap</td>       <td>和Query使用类似</td>      </tr>      <tr>       <td>@Url</td>       <td>指定请求路径</td>      </tr>     </tbody>    </table>    <p>请求和响应格式注解</p>    <table>     <thead>      <tr>       <th>名称</th>       <th>说明</th>      </tr>     </thead>     <tbody>      <tr>       <td>@FormUrlEncoded</td>       <td>表示请求发送编码表单数据,每个键值对需要使用@Field注解</td>      </tr>      <tr>       <td>@Multipart</td>       <td>表示请求发送multipart数据,需要配合使用@Part</td>      </tr>      <tr>       <td>@Streaming</td>       <td>表示响应用字节流的形式返回.如果没使用该注解,默认会把数据全部载入到内存中.该注解在在下载大文件的特别有用</td>      </tr>     </tbody>    </table>    <p>使用</p>    <p>这里不对注解进行过多的说明了,只展示几个用法</p>    <pre>  <code class="language-java">/**   * Created by caihan on 2017/1/11.   *   * @GET 表明这是get请求   * @POST 表明这是post请求   * @PUT 表明这是put请求   * @DELETE 表明这是delete请求   * @PATCH 表明这是一个patch请求,该请求是对put请求的补充,用于更新局部资源   * @HEAD 表明这是一个head请求   * @OPTIONS 表明这是一个option请求   * @HTTP 通用注解, 可以替换以上所有的注解,其拥有三个属性:method,path,hasBody   * @Headers 用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在   * @Header 作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头   * @Body 多用于post请求发送非表单数据, 比如想要以post方式传递json格式数据   * @Filed 多用于post请求中表单字段, Filed和FieldMap需要FormUrlEncoded结合使用   * @FiledMap 和@Filed作用一致,用于不确定表单参数   * @Part 用于表单字段, Part和PartMap与Multipart注解结合使用, 适合文件上传的情况   * @PartMap 用于表单字段, 默认接受的类型是Map<String,REquestBody>,可用于实现多文件上传   * <p>   * Part标志上文的内容可以是富媒体形势,比如上传一张图片,上传一段音乐,即它多用于字节流传输。   * 而Filed则相对简单些,通常是字符串键值对。   * </p>   * Part标志上文的内容可以是富媒体形势,比如上传一张图片,上传一段音乐,即它多用于字节流传输。   * 而Filed则相对简单些,通常是字符串键值对。   * @Path 用于url中的占位符,{占位符}和PATH只用在URL的path部分,url中的参数使用Query和QueryMap代替,保证接口定义的简洁   * @Query 用于Get中指定参数   * @QueryMap 和Query使用类似   * @Url 指定请求路径   */  public interface MyRetrofit2Service {        //使用@Headers添加多个请求头      @Headers({              "User-Agent:android",              "apikey:123456789",      })      @POST()      Call<HttpResult<News>> post(@Url String url, @QueryMap Map<String, String> map);        @GET("mobile/active")      Call<HttpResult<News>> get(@Header("token") String token, @Query("id") int activeId);        @GET("active/list")      Call<HttpResult<News>> ActiveList();        @POST("api/news/newsList")      Call<HttpResult<News>> post2(@QueryMap Map<String, String> map);        /**       * 很多情况下,我们需要上传json格式的数据。比如当我们注册新用户的时候,因为用户注册时的数据相对较多,       * 并可能以后会变化,这时候,服务端可能要求我们上传json格式的数据。此时就要@Body注解来实现。       * 直接传入实体,它会自行转化成Json       *       * @param url       * @param post       * @return       */      @POST("api/{url}/newsList")      Call<HttpResult<News>> login(@Path("url") String url, @Body News post);        /**       * 单张图片上传       * retrofit 2.0的上传和以前略有不同,需要借助@Multipart注解、@Part和MultipartBody实现。       *       * @param url       * @param file       * @return       */      @Multipart      @POST("{url}")      Call<HttpResult<News>> upload(@Path("url") String url, @Part MultipartBody.Part file);        /**       * 多张图片上传       *       * @param map       * @return       */      @Multipart      @POST("upload/upload")      Call<HttpResult<News>> upload(@PartMap Map<String, MultipartBody.Part> map);        /**       * 图文混传       *       * @param post       * @param map       * @return       */      @Multipart      @POST("")      Call<HttpResult<News>> register(@Body News post, @PartMap Map<String, MultipartBody.Part> map);        /**       * 文件下载       *       * @param fileUrl       * @return       */      @Streaming      @GET      Call<HttpResult<News>> downloadPicture(@Url String fileUrl);        /**       * 这里需要注意的是如果下载的文件较大,比如在10m以上,那么强烈建议你使用@Streaming进行注解,否则将会出现IO异常.       *       * @param fileUrl       * @return       */      @Streaming      @GET      Observable<HttpResult<News>> downloadPicture2(@Url String fileUrl);        @POST()      @FormUrlEncoded      Observable<HttpResult<News>> executePost(@FieldMap Map<String, Object> maps);  }</code></pre>    <p>踩坑</p>    <p>1.url被转义</p>    <pre>  <code class="language-java">http://api.mydemo.com/api%2Fnews%2FnewsList?</code></pre>    <p>罪魁祸首@Url与@Path注解,我们开发过程中,肯定会需要动态的修改请求地址</p>    <p>两种动态修改方式如下:</p>    <pre>  <code class="language-java">@POST()      Call<HttpResult<News>> post(@Url String url, @QueryMap Map<String, String> map);        @POST("api/{url}/newsList")      Call<HttpResult<News>> login(@Path("url") String url, @Body News post);</code></pre>    <p>第一种是直接使用@Url,它相当于直接替换了@POST()里面的请求地址</p>    <p>第二种是使用@Path("url"),它只替换了@POST("api/{url}/newsList")中的{url}</p>    <p>如果你用下面这样写的话,就会出现url被转义</p>    <pre>  <code class="language-java">@POST("{url}")      Call<HttpResult<News>> post(@Path("url") String url);</code></pre>    <p>你如果执意要用@Path,也不是不可以,需要这样写</p>    <pre>  <code class="language-java">@POST("{url}")      Call<HttpResult<News>> post(@Path(value = "url", encoded = true) String url);</code></pre>    <h3>OkHttpClient</h3>    <p>拦截器</p>    <p>addNetworkInterceptor添加的是网络拦截器Network Interfacetor它会在request和response时分别被调用一次;</p>    <p>addInterceptor添加的是应用拦截器Application Interceptor他只会在response被调用一次。</p>    <p>1.日志拦截器</p>    <p>使用 addNetworkInterceptor() 方法添加到 <strong>OkHttpClient</strong> 中</p>    <p>日志拦截器我这有两种创建方式:</p>    <p>一种是使用HttpLoggingInterceptor,需要使用到依赖</p>    <pre>  <code class="language-java">compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'</code></pre>    <pre>  <code class="language-java">/**       * 创建日志拦截器       *       * @return       */      public static HttpLoggingInterceptor getHttpLoggingInterceptor() {          HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(                  new HttpLoggingInterceptor.Logger() {                        @Override                      public void log(String message) {                          Log.e("OkHttp", "log = " + message);                      }                    });          loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);          return loggingInterceptor;      }</code></pre>    <p>另一种是</p>    <pre>  <code class="language-java">/**       * 创建日志拦截器       *       * @return       */      private class LogInterceptor implements Interceptor {          @Override          public okhttp3.Response intercept(Chain chain) throws IOException {              Request request = chain.request();                long t1 = System.nanoTime();              Log.d("OkHttp", "HttpHelper1" + String.format("Sending request %s on %s%n%s",                      request.url(), chain.connection(), request.headers()));                okhttp3.Response response = chain.proceed(request);              long t2 = System.nanoTime();                Log.d("OkHttp", "HttpHelper2" + String.format("Received response for %s in %.1fms%n%s",                      response.request().url(), (t2 - t1) / 1e6d, response.headers()));              return response;          }      }</code></pre>    <p>2.请求头拦截器</p>    <p>使用 addInterceptor() 方法添加到 <strong>OkHttpClient</strong> 中</p>    <p>我的理解是,请求头拦截器是为了让服务端能更好的识别该请求,服务器那边通过请求头判断该请求是否为有效请求等...</p>    <pre>  <code class="language-java">/**       * 拦截器Interceptors       * 使用addHeader()不会覆盖之前设置的header,若使用header()则会覆盖之前的header       *       * @return       */      public static Interceptor getRequestHeader() {          Interceptor headerInterceptor = new Interceptor() {                @Override              public okhttp3.Response intercept(Chain chain) throws IOException {                  Request originalRequest = chain.request();                  Request.Builder builder = originalRequest.newBuilder();                  builder.addHeader("version", "1");                  builder.addHeader("time", System.currentTimeMillis() + "");                    Request.Builder requestBuilder = builder.method(originalRequest.method(), originalRequest.body());                  Request request = requestBuilder.build();                  return chain.proceed(request);              }            };          return headerInterceptor;      }</code></pre>    <p>3.统一请求拦截器</p>    <p>使用 addInterceptor() 方法添加到 <strong>OkHttpClient</strong> 中</p>    <p>统一请求拦截器的功能跟请求头拦截器相类似</p>    <pre>  <code class="language-java">/**       * 拦截器Interceptors       * 统一的请求参数       */      private Interceptor commonParamsInterceptor() {          Interceptor commonParams = new Interceptor() {              @Override              public okhttp3.Response intercept(Chain chain) throws IOException {                  Request originRequest = chain.request();                  Request request;                  HttpUrl httpUrl = originRequest.url().newBuilder().                          addQueryParameter("paltform", "android").                          addQueryParameter("version", "1.0.0").build();                  request = originRequest.newBuilder().url(httpUrl).build();                  return chain.proceed(request);              }          };            return commonParams;      }</code></pre>    <p>4.时间拦截器</p>    <p>使用 addInterceptor() 方法添加到 <strong>OkHttpClient</strong> 中</p>    <p>从响应中获取服务器返回的时间</p>    <pre>  <code class="language-java">/**       * 拦截器Interceptors       * 通过响应拦截器实现了从响应中获取服务器返回的time       *       * @return       */      public static Interceptor getResponseHeader() {          Interceptor interceptor = new Interceptor() {                @Override              public okhttp3.Response intercept(Chain chain) throws IOException {                  okhttp3.Response response = chain.proceed(chain.request());                  String timestamp = response.header("time");                  if (timestamp != null) {                      //获取到响应header中的time                  }                  return response;              }          };          return interceptor;      }</code></pre>    <h3>缓存</h3>    <p>使用okhttp缓存的话,先要创建Cache,然后在创建缓存拦截器</p>    <pre>  <code class="language-java">//创建Cache          File httpCacheDirectory = new File(MyApp.getContext().getCacheDir(), "OkHttpCache");          Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);          httpClientBuilder.cache(cache);          //设置缓存          httpClientBuilder.addNetworkInterceptor(getCacheInterceptor2());          httpClientBuilder.addInterceptor(getCacheInterceptor2());</code></pre>    <p>缓存拦截器</p>    <p>缓存时间自己根据情况设定</p>    <pre>  <code class="language-java">/**       * 在无网络的情况下读取缓存,有网络的情况下根据缓存的过期时间重新请求,       *       * @return       */      public Interceptor getCacheInterceptor2() {          Interceptor commonParams = new Interceptor() {              @Override              public okhttp3.Response intercept(Chain chain) throws IOException {                  Request request = chain.request();                  if (!NetworkUtils.isConnected()) {                      //无网络下强制使用缓存,无论缓存是否过期,此时该请求实际上不会被发送出去。                      request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE)                              .build();                  }                    okhttp3.Response response = chain.proceed(request);                  if (NetworkUtils.isConnected()) {//有网络情况下,根据请求接口的设置,配置缓存。                      //这样在下次请求时,根据缓存决定是否真正发出请求。                      String cacheControl = request.cacheControl().toString();                      //当然如果你想在有网络的情况下都直接走网络,那么只需要                      //将其超时时间这是为0即可:String cacheControl="Cache-Control:public,max-age=0"                      int maxAge = 60 * 60; // read from cache for 1 minute                      return response.newBuilder()  //                            .header("Cache-Control", cacheControl)                              .header("Cache-Control", "public, max-age=" + maxAge)                              .removeHeader("Pragma")                              .build();                  } else {                      //无网络                      int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale                      return response.newBuilder()  //                            .header("Cache-Control", "public,only-if-cached,max-stale=360000")                              .header("Cache-Control", "public,only-if-cached,max-stale=" + maxStale)                              .removeHeader("Pragma")                              .build();                  }                }          };          return commonParams;      }</code></pre>    <h3>自定义CookieJar</h3>    <pre>  <code class="language-java">httpClientBuilder.cookieJar(new CookieJar() {              final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();                @Override              public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {                  cookieStore.put(url, cookies);//保存cookie                  //也可以使用SP保存              }                @Override              public List<Cookie> loadForRequest(HttpUrl url) {                  List<Cookie> cookies = cookieStore.get(url);//取出cookie                  return cookies != null ? cookies : new ArrayList<Cookie>();              }          });</code></pre>    <h2>启动Retrofit2</h2>    <p>到了这里,基本上准备工作都做好了,可以启动Retrofit2了</p>    <pre>  <code class="language-java">retrofit = new Retrofit.Builder()                  .client(httpClientBuilder.build())                  .baseUrl(BASE_URL)                  .addCallAdapterFactory(RxJavaCallAdapterFactory.create())                  .addConverterFactory(FastJsonConverterFactory.create())                  .build();          mApi = retrofit.create(MyRetrofit2Service.class);</code></pre>    <p>界面上通过getEnqueue()方法调用</p>    <pre>  <code class="language-java">/**       * 异步调用       */      public void getEnqueue() {          Call<HttpResult<News>> call = mApi.post(NEWS_URI, params);          call.enqueue(new Callback<HttpResult<News>>() {              @Override              public void onResponse(Call<HttpResult<News>> call, Response<HttpResult<News>> response) {                  //处理请求成功                  Log.e("OkHttp", "处理成功请求 response = " + response.body().toString());              }                @Override              public void onFailure(Call<HttpResult<News>> call, Throwable t) {                  //处理请求失败                  Log.e("OkHttp", "处理失败请求");              }          });  //        cancelCall(call);      }</code></pre>    <h2>结束</h2>    <p>相信到这边应该能满足Demo的要求了吧,至少用是可以用了,不过在实际开发中,这还是太糙了点,哎..没时间打磨,慢慢来吧,有兴趣的可以留言讨论下优化方案,大家拓展下思维也是不错的嘛</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/73216939806a</p>    <p> </p>