Retrofit2.0起步篇

mm975829 7年前
   <p>retrofit 英文名字是改装的意思,也就是说他是对网络请求的一种改装,他不负责进行网络请求,他是对请求方式的一种封装。真正进行网络请求的是okhttp。</p>    <p>以下所有内容在Android Studio已经导入retrofit为基础。导入方式如下:</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:converter-scalars:2.1.0'</code></pre>    <h2><strong>利用Retrofit进行简单的GET请求</strong></h2>    <p>retrofit在构建请求方式之前,需要构建一个接口方法,通过这个接口方法的返回值,来进行网络请求。</p>    <p>下面,来通过一些简单的例子了解GET请求。</p>    <h3><strong>实验一:对一个简单的html页面进行GET请求</strong></h3>    <p>我们要获取百度页面的HTML。首先构建如下接口:</p>    <pre>  <code class="language-java">public interface HtmlService {      @GET("/")      Call<String> getHtml();  }</code></pre>    <p>注意,GET注解中的参数,和方法中的参数至少要加一个,否则会报错。由于,我们只需要请求 www.baidu.com ,所以get这里不需要加参数,就是 /</p>    <p>然后,我们通过如下步骤,来进行网络请求。</p>    <p>在我们需要进行网络请求的类中,通过以下的步骤,进行网络请求:</p>    <ol>     <li>构建retrofit对象。</li>     <li>动态代理生成接口的对象。</li>     <li>通过接口的方法,得到要请求的API的调用。</li>     <li>通过同步/异步的方式,得到response。</li>     <li>根据需要,处理response。</li>    </ol>    <p>第一步</p>    <pre>  <code class="language-java">Retrofit retrofit = new Retrofit.Builder().          addConverterFactory(ScalarsConverterFactory.create()).                  baseUrl("https://www.baidu.com").                  build();</code></pre>    <p>通过以上代码,可以简单的构建一个retrofit对象, addConverterFactory 是对response进行解析,里面添加的参数是表示对response用String解析,然后添加一个基础的URL,后续的参数则是通过上面我们定制的接口来添加,最后构建一个完整的URL。</p>    <p>第二步</p>    <pre>  <code class="language-java">HtmlService htmlService = retrofit.create(HtmlService.class);</code></pre>    <p>通过动态代理,生成一个接口的对象。</p>    <p>第三步</p>    <pre>  <code class="language-java">Call<String> call = htmlService.getHtml();</code></pre>    <p>通过接口的方法得到调用的对象。</p>    <p>第四步与第五步</p>    <p>异步方法得到response:</p>    <pre>  <code class="language-java">call.enqueue(new Callback<String>() {              @Override              public void onResponse(Call<String> call, Response<String> response) {                  showText.append(response.body());              }                @Override              public void onFailure(Call<String> call, Throwable t) {                  Toast.makeText(MainActivity.this,t.getMessage(),Toast.LENGTH_SHORT).show();              }          });</code></pre>    <p>得到的response,通过 response.body() 得到响应报文的body部分。</p>    <p>同步方法得到response:</p>    <pre>  <code class="language-java">new Thread(new Runnable() {              @Override              public void run() {                  try {                      final String str = call.execute().body();                      handler.post(new Runnable() {                          @Override                          public void run() {                              showText.append(str);                          }                      });                  } catch (IOException e) {                      e.printStackTrace();                  }              }          }).start();</code></pre>    <h3>实验二:对一个返回JSON格式的API进行GET请求</h3>    <p>通过GET请求 GankIO的api 得到Json:</p>    <p>首先,我们也是通过接口,构建一个接口方法:</p>    <pre>  <code class="language-java">@GET("content/{number}/{page}")  Call<HistoryBean> getHistoryData(@Path("number") String number,@Path("page") String page);</code></pre>    <p>这里,方法里面传入的参数会放到 @GET 的注解里面。</p>    <p>然后,重新构建一个retrofit对象:</p>    <pre>  <code class="language-java">Retrofit retrofit = new Retrofit.Builder().                addConverterFactory(GsonConverterFactory.create()).                  baseUrl("http://gank.io/api/history/").                  build();</code></pre>    <p>这里面添加的解析器是 GsonConverterFactory ,表示对response中body提供对象解析。然后的方法和上面类似:</p>    <pre>  <code class="language-java">HtmlService htmlService = retrofit.create(HtmlService.class);          call = htmlService.getHistoryData("2", "1");  call.enqueue(new Callback<HistoryBean>() {              @Override              public void onResponse(Call<HistoryBean> call, Response<HistoryBean> response) {                  HistoryBean hb = response.body();                  if(hb == null) return;                  showText.append(hb.isError() + "");                  for(HistoryBean.ResultsBean rb : hb.getResults()){                      showText.append(rb.getTitle() + "/n");                  }              }                @Override              public void onFailure(Call<HistoryBean> call, Throwable t) {                }          });</code></pre>    <p>上面的方法是异步得到的,同步的方法和上面类似,就不多说了。</p>    <h3><strong>实验三:添加一个请求参数构建GET请求</strong></h3>    <p>上面的GET方法是没有查询参数的,下面对一个有查询参数的api,进行GET请求,这里我们利用 豆瓣的搜索图书的API</p>    <p>这个API接受4个搜索参数,具体如下:</p>    <table>     <thead>      <tr>       <th>参数</th>       <th>意义</th>       <th>备注</th>      </tr>     </thead>     <tbody>      <tr>       <td>q</td>       <td>查询关键字</td>       <td>q与tag必传其一</td>      </tr>      <tr>       <td>tag</td>       <td>查询的tag</td>       <td>q与tag必传其一</td>      </tr>      <tr>       <td>start</td>       <td>取结果的offset</td>       <td>默认为0</td>      </tr>      <tr>       <td>count</td>       <td>取结果的条数</td>       <td>默认为20</td>      </tr>     </tbody>    </table>    <p>首先,我们也是构建一个请求接口的方法:</p>    <pre>  <code class="language-java">@GET("book/search")  Call<BookBean> queryBookInfo(@Query("q") String name);</code></pre>    <p>在这里面,我们用到了一个新的注解参数 @Query 这个参数表示请求参数会以键值对的形式拼接在URL后面。</p>    <p>这样的方式,有一种局限性,因为要在每个GET接口方法里面写入键值对信息,如果,有些键值对信息是每个GET方法都需要的,我们就会通过一个拦截器的形式,统一在请求的时候加上。步骤如下:</p>    <ol>     <li>自定义一个拦截器实现 Interceptor 。</li>     <li>创建retrofit的客户端(上面的代码都是默认的客户端),加上这个拦截器。</li>    </ol>    <p>第一步</p>    <pre>  <code class="language-java">public class CustomInterceptor implements Interceptor {      @Override      public Response intercept(Chain chain) throws IOException {          Request request = chain.request();          HttpUrl httpUrl = request.url().newBuilder()                  .addQueryParameter("token", "tokenValue")                  .build();          request = request.newBuilder().url(httpUrl).build();          return chain.proceed(request);      }  }</code></pre>    <p>第二步</p>    <pre>  <code class="language-java">OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder().                  addInterceptor(new CustomInterceptor()).                  connectTimeout(1000, TimeUnit.MILLISECONDS);    Retrofit retrofit = new Retrofit.Builder().                  client(httpClientBuilder.build()).                  addConverterFactory(GsonConverterFactory.create()).                  baseUrl("https://api.douban.com/v2/").                  build();  HtmlService htmlService = retrofit.create(HtmlService.class);  call = htmlService.queryBookInfo("第一行代码");</code></pre>    <p>后续的异步请求基本一致,就不细说了。</p>    <h3><strong>实验四:添加多种请求参数构建GET请求</strong></h3>    <p>实验三的部分,讲了对一个查询参数和一个共有的查询参数的GET请求构建方法,下面多个查询参数的GET请求,看看是否有简单的方式,因为不想在一个方法里,传入4个以上的参数。</p>    <p>请求的API还是上边的豆瓣的搜索API,他正好有4个请求参数</p>    <p>下面,看如下构建方式:</p>    <pre>  <code class="language-java">@GET("book/search")  Call<BookBean> queryBookInfo(@QueryMap Map<String,String> options);</code></pre>    <p>然后,将请求参数通过键值对的形式保存到Map里:</p>    <pre>  <code class="language-java">Map<String,String> options = new HashMap<>();   options.put("q","第一行代码");   options.put("start","0");   options.put("count","1");   call = htmlService.queryBookInfo(options);</code></pre>    <p>在上面的情况下,有多种键值对,每一种key对应的value都是唯一的,retrofit也支持相同的key,却有多种value的形式。方式如下:</p>    <pre>  <code class="language-java">@GET("book/search")  Call<BookBean> queryBookInfo(@Query("key") List<String> value);</code></pre>    <p>然后,将value的集合传入方法中,后续的步骤不变,就不多数。</p>    <h2><strong>利用Retrofit进行简单的POST请求</strong></h2>    <p>利用retorfit进行post请求与进行get请求没有太多的区别。主要的区别就在构建接口方法上面,有一些不同,下面通过一些实验来看一下具体的区别。</p>    <h3><strong>实验一:将少数参数放到请求体中进行POST请求</strong></h3>    <p>下面的POST方法API是我自己写的后台来接受简单的POST,就不放出来了。</p>    <p>首先,也是构建一个接口方法:</p>    <pre>  <code class="language-java">@FormUrlEncoded  @POST("login")  Call<String> doLogin(@Field("name")String name,@Field("password") String password);</code></pre>    <p>第一个注解,表示自动将请求参数的类型调整为 application/x-www-form-urlencoded ,如果方法参数的注解用了 @Field 就一定要用 @FormUrlEncoded 。POST注解里面依旧放的是API,方法参数通过 @Field 将请求参数放置在请求体中。</p>    <p>后续创建retrofit对象,创建call对象,发起请求,都是和GET方法一样,就不多说了。</p>    <h3><strong>实验二:将多个参数放到请求体中进行POST请求</strong></h3>    <p>这个只不过构建接口方法的时候,有所区别,其他步骤和多种参数进行GET请求一样。</p>    <pre>  <code class="language-java">@FormUrlEncoded  @POST("login")  Call<String> doLogin(@FieldMap Map<String,String> fields);</code></pre>    <p>将多个请求参数保存到Map中,传入方法中,没什么好说的。</p>    <h3><strong>实验三:利用POST进行文件上传</strong></h3>    <p>同样构建一个接口方法:</p>    <pre>  <code class="language-java">@Multipart  @POST("upload")  Call<ResponseBody> uploadFile(@Part("description") RequestBody description,  @Part MultipartBody.Part file);</code></pre>    <p>这里的POST修饰注解就换成了 @Multipart ,在方法参数里面,通过 @Part 注解来修饰参数。</p>    <p>我们说一下具体怎么构建这些方法的参数。</p>    <p>首先,构建一个RequestBody对象的description,构建方式如下:</p>    <pre>  <code class="language-java">RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), "这是一个文件");</code></pre>    <p>然后,构建一个 MultipartBody.Part 对象的file,不过在构建之前,先要创建一个RequestBody对象,通过这个对象才能创建一个 MultipartBody.Part 对象。代码如下:</p>    <pre>  <code class="language-java">RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"),file);  MultipartBody.Part body = MultipartBody.Part.createFormData("file",file.getName(),requestBody);</code></pre>    <p>然后和其他方法一样,利用这些对象,生成call,然后进行网络请求。</p>    <pre>  <code class="language-java">call = service.uploadFile(description,body);</code></pre>    <p>当然,retrofit支持多种上传图片的方式,其构建方式如下:</p>    <pre>  <code class="language-java">// 上传多个文件      @Multipart      @POST("upload")      Call<ResponseBody> uploadMultipleFiles(              @Part("description") RequestBody description,              @Part MultipartBody.Part file1,              @Part MultipartBody.Part file2);</code></pre>    <p>以上这些实验就是利用Retrofit实现简单POST 请求。</p>    <h2><strong>添加HEADER</strong></h2>    <p>我们在进行复杂的网络请求的时候,通常要对请求添加头部,retrofit提供了多种添加方式,可以分为如下两种:</p>    <ol>     <li>静态添加头部</li>     <li>动态添加头部</li>    </ol>    <h3><strong>静态添加头部</strong></h3>    <p>静态添加头部,则每次请求的时候,头部的信息都是固定的,不可以更改的。添加静态头部的方式也有多种,方法如下:</p>    <ol>     <li>在接口中添加。</li>     <li>通过拦截器添加。</li>    </ol>    <p>下面,我们分别说一说每一种。</p>    <p>在接口中添加</p>    <p>还是以上文写到的接口为例,添加 Cache-Control , User-Agent 请求头部。</p>    <pre>  <code class="language-java">@Headers({              "Cache-Control: max-age=640000",              "User-Agent: app-name"      })</code></pre>    <p>通过拦截器添加</p>    <p>和上面统一处理GET参数的定制器一样,同样实现 Interceptor ,代码如下:</p>    <pre>  <code class="language-java">public class RequestInterceptor implements Interceptor {      @Override      public Response intercept(Chain chain) throws IOException {          Request original = chain.request();          Request request = original.newBuilder()                  .header("User-Agent", "app-name")                  .header("Cache-Control", "max-age=640000")                  .method(original.method(), original.body())                  .build();          return chain.proceed(request);      }  }</code></pre>    <p>然后,在创建okHttp的客户端时,把拦截器加进去,创建retrofit对象时,指定该客户端即可。</p>    <pre>  <code class="language-java">OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder().                  addInterceptor(new CustomInterceptor()).                  addInterceptor(new RequestInterceptor()).                  connectTimeout(1000, TimeUnit.MILLISECONDS);</code></pre>    <h3><strong>动态添加头部</strong></h3>    <p>动态添加的好处,就在于不同的请求会有不同的请求头部,那么可想而知,其添加的部分就是在接口方法里面。</p>    <pre>  <code class="language-java">@GET("{number}/{page}")  Call<HistoryBean> getHistoryData(@Header("Content-Range") String contentRange ,@Path("number") String number, @Path("page") String page);</code></pre>    <p>后续的使用都是一样的,没什么好说的。</p>    <h2><strong>设置网络请求日志</strong></h2>    <p>retrofit提供了对网络请求的过程进行打印的日志的插件,需要单独的导入,其方式如下:</p>    <pre>  <code class="language-java">compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'</code></pre>    <p>通过上面的导入代码,可以看出这个日志插件是okHttp特有的,这也可以证明,retrofit只是封装了请求的方式,真正请求的还是通过okHttp。那么我们也可以猜出,其设置的方式必然也是通过拦截器,放进okHttp的客户端里面。具体的代码如下:</p>    <pre>  <code class="language-java">HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();  httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);    OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder().                  addInterceptor(new CustomInterceptor()).                  addInterceptor(new RequestInterceptor()).                  addInterceptor(httpLoggingInterceptor).                  connectTimeout(1000, TimeUnit.MILLISECONDS);</code></pre>    <p>注意上面的代码,在设置请求级别上面设置为body,下面说一下具体的级别以及其内涵:</p>    <ol>     <li>NONE : 没有任何日志信息。</li>     <li>Basic : 打印请求类型,URL,请求体大小,返回值状态以及返回值的大小。</li>     <li>Headers : 打印返回请求和返回值的头部信息,请求类型,URL以及返回值状态码</li>     <li>Body : 打印请求和返回值的头部和body信息。</li>    </ol>    <h2><strong>总结</strong></h2>    <p>上面就是简单的retrofit的使用,关于利用retrofit结合其他部分如Rx,okHttp等等,或者利用retrofit实现多种文件上传,下载功能,保存cookie等等功能,可以期待后续的文章。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/48206f0104d0</p>    <p> </p>