retrofit使用

Retrofit使用

配置和初始化

结合OkHttp搭建网络框架

OkHttp配置

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        client = new OkHttpClient.Builder()
                .addInterceptor(interceptor)
                .retryOnConnectionFailure(true)
                .connectTimeout(15, TimeUnit.SECONDS)
                .addNetworkInterceptor(authorizationInterceptor)
                .build();

其中 level 为 BASIC / HEADERS / BODY
retryOnConnectionFailure:错误重连
addInterceptor:设置应用拦截器,可用于设置公共参数,头信息,日志拦截等

addNetworkInterceptor:网络拦截器,可以用于重试或重写。
参考:Interceptors
中文翻译:Okhttp-wiki 之 Interceptors 拦截器

Retrofit配置

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(client)
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();
        apiService = retrofit.create(ApiService.class);

定义接口

public interface GitHubService {
    @GET("users/{user}/repos")
    Observable<List<Repo>> listRepos(@Path("user") String user);
}

//获取实例
GitHubService service = retrofit.create(GitHubService.class);
//Http request
Observable<List<Repo>> call = service.listRepos("octocat");
call.subscribe(...)

注解使用

方法注解

  • @GET
  • @POST
  • @PUT
  • @DELETE

  • @PATH
    URL占位符,用于替换和动态更新,相应的参数必须使用相同的字符串被@Path进行注释

  • @HEAD

  • @OPTIONS

  • @HTTP

    @HTTP(method = "get", path = "users/{user}", hasBody = false)
    

    标记注解

  • @FormUrlEncoded

  • @Multipart

  • @Streaming
    用于下载大文件

    @Streaming
    @GET
    Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);
    
    ResponseBody body = response.body();
    long fileSize = body.contentLength();
    InputStream inputStream = body.byteStream();
    

参数注解

  • @Query
    查询参数,用于GET查询

    @GET("group/users")
    Call<List<User>> groupList(@Query("id") int groupId);
    //--> http://baseurl/group/users?id=groupId
    
  • @QueryMap
    @QueryMap可以约定是否需要encode

    @GET("group/users")
    Call<List<News>> getNews((@QueryMap(encoded=true) Map<String, String> options);
    
  • @Body
    用于POST请求体,将实例对象根据转换方式转换为对应的数据形式,比如ProtoConverterFactory结合protocolBuff的时候用这个

    @FormUrlEncoded
    @POST("user/edit")
    Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
    
  • @Field
    Post方式传递简单的键值对,需要添加@FormUrlEncoded(Content-Type:application/x-www-form-urlencoded)表示表单提交

  • @FieldMap

  • @Part
    用于POST文件上传,其中@Part MultipartBody.Part代表文件,@Part("key") RequestBody代表参数。需要添加@Multipart表示支持文件上传的表单,Content-Type: multipart/form-data

    @Multipart
    @POST("upload")
    Call<ResponseBody> upload(@Part("description") RequestBody description, @Part MultipartBody.Part file);
    // https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java
    // use the FileUtils to get the actual file by uri
    File file = FileUtils.getFile(this, fileUri);
    
    // create RequestBody instance from file
    RequestBody requestFile = RequestBody.create(MediaType.parse("image/png"), file);
    
    // MultipartBody.Part is used to send also the actual file name
    MultipartBody.Part body = MultipartBody.Part.createFormData("picture", file.getName(), requestFile);
    

    这里可能有人疑惑Filed和Part的区别,请参考:四种常见的 POST 提交数据方式

  • @PartMap
    多文件上传

其他注解

  • @Path
  • @Header

    //动态设置Header值
    @GET("user")
    Call<User> getUser(@Header("Authorization") String authorization)
    等同于 :
    
    //静态设置Header值
    //这里authorization就是上面方法里传进来变量的值
    @Headers("Authorization: authorization")
    @GET("widget/list")
    Call<User> getUser()
    
  • @Headers
    Headers 用于修饰方法,用于设置多个Header值

    @Headers({
        "Accept: application/vnd.github.v3.full+json",
        "User-Agent: Retrofit-Sample-App"
    })
    @GET("users/{username}")
    Call<User> getUser(@Path("username") String username);
    
  • @Url
    动态 URL 参数是让我头疼多年的一个问题,现在我们终于解决了!如果你向 GitHub 发出多个请求,收到一个响应,通常这个响应大概像下面这样:

    interface GitHubService {
      @GET("/repos/{owner}/{repo}/contributors")
      Call<List<Contributor>> repoContributors(
          @Path("owner") String owner,
          @Path("repo") String repo);
    }
    
    Call<List<Contributor>> call =
        gitHubService.repoContributors("square", "retrofit");
    Response<List<Contributor>> response = call.execute();
    
    // HTTP/1.1 200 OK
    // Link: <https://api.github.com/repositories/892275/contributors?
    page=2>; rel="next", <https://api.github.com/repositories/892275/
    contributors?page=3>; rel="last"
    // ...
    

    要是你想做分页,你就得自己去分析这些 URL 了。GitHub 可能将 header link 地址列表里的数据已经缓存在服务器内存里了,当你去按他们指引的地址去请求的话,他们就不必费劲去从数据库里给你拿数据了,速度上也更快。但是,在 Retrofit 1.0 的时候,我们没有办法去直接执行 GitHub Server 返回在 header 里的请求地址。

    用上我们新的 response 类型后,不止是我刚才提到的那些元数据,我们还可以写一些方法来读出自定义的字段,比如上面例子里的下一页的地址:

    Response<List<Contributor>> response = call.execute();
    
    // HTTP/1.1 200 OK
    // Link: <https://api.github.com/repositories/892275/contributors?
    page=2>; rel="next", <https://api.github.com/repositories/892275/
    contributors?page=3>; rel="last"
    // ...
    
    String links = response.headers().get("Link");
    String nextLink = nextFromGitHubLinks(links);
    
    // https://api.github.com/repositories/892275/contributors?page=2
    

    这个可能和上面的接口生成地址略有不同。

    动态 URL 地址就是用在连续请求里的。在第一个请求之后,如果返回的结果里有指明下个请求的地址的话,在之前,你可能得单独写个 interface 来处理这种情况,现在就无需那么费事了。

    interface GitHubService {
      @GET("/repos/{owner}/{repo}/contributors")
      Call<List<Contributor>> repoContributors(
          @Path("owner") String owner,
          @Path("repo") String repo);
    
      @GET
      Call<List<Contributor>> repoContributorsPaginate(
          @Url String url);
    }
    

    Retrofit 2.0 有了新的 标注:@Url ,允许你直接传入一个请求的 URL。

    有了这个方法后,我们就可以直接把刚才取出来的下一页的地址传入,是不是一切都流畅了很多:

    String nextLink = nextFromGitHubLinks(links);
    
    // https://api.github.com/repositories/892275/contributors?page=2
    
    Call<List<Contributor>> nextCall =
        gitHubService.repoContributorsPaginate(nextLink);
    

    这样的话,我们就能通过调用 repoContributorsPaginate 来获取第二页内容,然后通过第二页的 header 来请求第三页。你可能很多的 API 都见到过类似的设计,这在 Retrofit 1 里确实是个困扰很多人的大麻烦。

文件上传

不用MultipartBody.Part这个类时,上传文件

public interface ApiInterface {
    @Multipart
    @POST ("/api/Accounts/editaccount")
    Call<User> editUser (@Part("file_key\"; filename=\"pp.png"), @Part("username") String username);
}

首先我们一点明确,因为这里使用了@ Multipart,那么我们认为@Part应当支持普通的key-value,以及文件。

对于普通的key-value是没问题的,只需要这样@Part("username") String username

那么对于文件,为什么需要这样呢?@Part("file_key\"; filename=\"pp.png")

这个value设置的值不用看就会觉得特别奇怪,然而却可以正常执行,原因是什么呢?

原因是这样的:

当上传key-value的时候,实际上对应这样的代码:

builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""),
                        RequestBody.create(null, params.get(key)));

也就是说,我们的@Part转化为了

Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"")

这么一看,很随意,只要把key放进去就可以了。

但是,retrofit2并没有对文件做特殊处理,文件的对应的字符串应该是这样的

 Headers.of("Content-Disposition", "form-data; name="filekey";filename="filename.png");

与键值对对应的字符串相比,多了个;filename="filename.png,就因为retrofit没有做特殊处理,所以你现在看这些hack的做法

参考

用Retrofit2简化HTTP请求
Retrofit2.0使用总结

发表评论

电子邮件地址不会被公开。 必填项已用*标注

返回主页看更多
狠狠的抽打博主 支付宝 扫一扫