Retrofit2整理

来源:互联网 发布:java字符串数组 \0 编辑:程序博客网 时间:2024/06/14 09:02

简介

Retrofit的介绍:

A type-safe REST client for Android and Java.
Retrofit使用注解来描述HTTP请求,默认支持URL参数替换和请求参数。而且还支持自定义header、multipart请求体、文件上传和下载、模拟响应等等。

导入

Retrofit2默认以OkHttp为网络层,因此不需要显式依赖OkHttp。但是Retrofit2需要同时依赖JSON转换器。例如以Gson为JSON转换器:

dependencies {      // Retrofit & OkHttp    compile 'com.squareup.retrofit2:retrofit:2.3.0'    compile 'com.squareup.retrofit2:converter-gson:2.3.0'    // 如果需要打印日志    compile 'com.squareup.okhttp3:logging-interceptor:3.8.0'  }

记得要添加网络权限:

<uses-permission android:name="android.permission.INTERNET" />  

使用示例

1. 定义接口

接口中定义了请求接口的方法,Retrofit会自动把请求结果解析成声明的返回值类型:

public interface GitHubClient {      @GET("/users/{user}/repos")    Call<List<GitHubRepo>> reposForUser(        @Path("user") String user    );}

GitHubRepo类:

public class GitHubRepo {      private int id;    private String name;    public GitHubRepo() {    }    public int getId() {        return id;    }    public String getName() {        return name;    }}

2. 创建Retrofit类

String API_BASE_URL = "https://api.github.com/";OkHttpClient.Builder httpClient = new OkHttpClient.Builder();Retrofit.Builder builder = new Retrofit.Builder()            .baseUrl(API_BASE_URL)            .addConverterFactory(GsonConverterFactory.create());Retrofit retrofit = builder        .client(httpClient.build())        .build();GitHubClient client =  retrofit.create(GitHubClient.class);  

3. 发送请求

// Create a very simple REST adapter which points the GitHub API endpoint.GitHubClient client =  retrofit.create(GitHubClient.class);// Fetch a list of the Github repositories.Call<List<GitHubRepo>> call =      client.reposForUser("fs-opensource");// 异步执行call.enqueue(new Callback<List<GitHubRepo>>() {      @Override    public void onResponse(Call<List<GitHubRepo>> call, Response<List<GitHubRepo>> response) {    }    @Override    public void onFailure(Call<List<GitHubRepo>> call, Throwable t) {    }})

如果要获取完整的请求结果,可以调用response.raw()。

请求方法详解

使用@GET, @POST(发送创建资源), @PUT(替换资源), @DELETE(删除资源), @PATCH(选择性更新资源)或者@HEAD注解表示使用的HTTP请求方法。例如:

public interface FutureStudioClient {      @GET    Call<ResponseBody> getUserProfilePhoto(        @Url String profilePhotoUrl    );    @PUT("/user/info")    Call<UserInfo> updateUserInfo(        @Body UserInfo userInfo    );    @DELETE("/user")    Call<Void> deleteUser();    // 也可以指定URL全称    @GET("https://futurestud.io/tutorials/rss/")    Call<FutureStudioRssFeed> getRssFeed();    @GET("/users/{user}/repos")    Call<List<GitHubRepo>> reposForUser(        // 使用这个参数来替换@GET注解中user这个占位符        @Path("user") String user    );    @GET("/tutorials")    Call<List<Tutorial>> getTutorials(        @Query("page") Integer page    );}

请求方法的注解需要传入URL地址参数,可以是相对地址也可以是地址全称。

Retrofit会自动把请求结果解析成请求方法的返回值,如果想要获取原始请求结果,可以把返回值类型声明为Call<Response>,这样可以节省解析成特定对象的事件;如果不需要返回请求结果,可以声明Call<Void>为返回值类型,这样可以节省加载response body的时间。

可以对请求方法的参数进行以下的注解:

  • @Body:发送Java对象作为请求体,Java对象会通过converter转为JSON发送
  • @Url:使用指定的Url
  • @Field:作为表单数据发送
  • @Path:替换方法注解中的路径占位符
  • @Query:指定查询资源的更详细条件,以?key=value的形式添加在Url后面。
  • @Field:后续详解。

全局配置Retrofit

可以自定义一个ServiceGenerator来封装Retrofit的初始化过程,在项目中使用同一个Retrofit,只使用一个socket连接来处理所有的请求。

public class ServiceGenerator {    private static final String BASE_URL = "https://api.github.com/";    private static Retrofit.Builder builder =            new Retrofit.Builder().baseUrl(BASE_URL)                .addConverterFactory(GsonConverterFactory.create());    private static Retrofit retrofit = builder.build();    private static OkHttpClient.Builder httpClient =            new OkHttpClient.Builder();    public static <S> S createService(Class<S> serviceClass) {        return retrofit.create(serviceClass);    }}

这样封装成ServiceGenerator之后,调用就很方便了:

GitHubClient client = ServiceGenerator.createService(GitHubClient.class);  

可以通过添加HttpLoggingInterceptor来打印日志,实现如下:

public class ServiceGenerator {    private static final String BASE_URL = "https://api.github.com/";    private static Retrofit.Builder builder =            new Retrofit.Builder()                    .baseUrl(BASE_URL)                     .addConverterFactory(GsonConverterFactory.create());    private static Retrofit retrofit = builder.build();    private static HttpLoggingInterceptor logging =            new HttpLoggingInterceptor()                    .setLevel(HttpLoggingInterceptor.Level.BODY);    private static OkHttpClient.Builder httpClient =            new OkHttpClient.Builder();    public static <S> S createService(        Class<S> serviceClass) {        if (!httpClient.interceptors().contains(logging)) {            httpClient.addInterceptor(logging);            builder.client(httpClient.build());            retrofit = builder.build();        }        return retrofit.create(serviceClass);    }}

这里在添加HttpLoggingInterceptor之前先判断了是否已经有添加了,避免重复添加。

Url处理

配置基本Url:

Retrofit.Builder builder = new Retrofit.Builder()              .baseUrl("https://your.base.url/api/");

注意基本Url必须以/结尾

端点Url可以在@GET注解参数中设置,也可以设置在方法参数注解的@Url:

public interface UserService {      @GET    public Call<ResponseBody> profilePicture(@Url String url);}

Retrofit2使用OkHttp的HttpUrl来处理Url,遵循以下几个原则:
1.当端点Url定义了scheme和host的时候,就会整个替换掉base Url。

2.如果端点Url以/开头,则端点Url会直接拼接到基本Url的host后面。比如说下例第二个Url中,端点Url是/my/endpoint,以/开头,会直接拼接到基本Url的host后面,也就是/futurestud.io/后面:

# Good Practicebase url: https://futurestud.io/api/  endpoint: my/endpoint  Result:   https://futurestud.io/api/my/endpoint# Bad Practicebase url: https://futurestud.io/api  endpoint: /my/endpoint  Result:   https://futurestud.io/my/endpoint

这样子可以用在API有版本区别的时候:

# Example 1base url: https://futurestud.io/api/v3/  endpoint: my/endpoint  Result:   https://futurestud.io/api/v3/my/endpoint# Example 2base url: https://futurestud.io/api/v3/  endpoint: /api/v2/another/endpoint  Result:   https://futurestud.io/api/v2/another/endpoint 

3.用//表示沿用基本Url的scheme:

# Example 3 — completely different urlbase url: http://futurestud.io/api/  endpoint: https://api.futurestud.io/  Result:   https://api.futurestud.io/# Example 4 — Keep the base url’s schemebase url: https://futurestud.io/api/  endpoint: //api.futurestud.io/  Result:   https://api.futurestud.io/# Example 5 — Keep the base url’s schemebase url: http://futurestud.io/api/  endpoint: //api.github.com  Result:   http://api.github.com  

4.方法参数注解中可以注解@Path对应@GET的Url中的{占位符}来实时替换。传入”“字符串表示省略该path,比如下面:

public interface TaskService {      @GET("tasks/{taskId}")    Call<List<Task>> getTasks(@Path("taskId") String taskId);}

如果传递的参数是"",那生成的Url就是tasks/。而服务器对于Url结尾有无/,处理方式是一样:

// 效果一样https://your.api.url/tasks  https://your.api.url/tasks/  

这样如果服务器的处理方式是对于有声明资源id就返回某一资源,如果没有声明资源id就返回资源列表的话,这种省略的机制就可以达到一个API可以实现两种用途。

但是如果所要替换的path是在Url中间的话就不能传递"",否则服务器一般识别不了。

注意@Path参数不能传递null。

自定义请求header

可以使用静态和动态两种方式来定义HTTP请求header。静态的含义是对于不同请求无法改变的,动态的含义是每个请求都需要指定。

1. 静态header

静态header的添加方式也有两种。一种是在请求方法上注解添加:

public interface UserService {      @Headers("Cache-Control: max-age=640000")    @GET("/tasks")    Call<List<Task>> getTasks();}

添加多个header:

public interface UserService {      @Headers({        "Accept: application/vnd.yourapi.v1.full+json",        "User-Agent: Your-App-Name"    })    @GET("/tasks/{task_id}")    Call<Task> getTask(@Path("task_id") long taskId);}

另一种添加静态header的方式就是在RequestInterceptor中设置,这样添加的header对于所有使用这个Retrofit实例进行的请求都生效:

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();  httpClient.addInterceptor(new Interceptor() {      @Override    public Response intercept(Interceptor.Chain chain) throws IOException {        Request original = chain.request();        Request request = original.newBuilder()            .header("User-Agent", "Your-App-Name")            .header("Accept", "application/vnd.yourapi.v1.full+json")            .method(original.method(), original.body())            .build();        return chain.proceed(request);    }}OkHttpClient client = httpClient.build();  Retrofit retrofit = new Retrofit.Builder()      .baseUrl(API_BASE_URL)    .addConverterFactory(GsonConverterFactory.create())    .client(client)    .build();

调用header()会覆盖原有的同名header,调用addHeader()是如果有同名header不会覆盖,而是添加到一起。

2. 动态header

动态header是添加在请求方法的参数当中:

public interface UserService {      @GET("/tasks")    Call<List<Task>> getTasks(@Header("Content-Range") String contentRange);}

可以使用Map批量添加:

public interface TaskService {      @GET("/tasks")    Call<List<Task>> getTasks(        @HeaderMap Map<String, String> headers    );}

Get请求和表单Post请求

1. Get请求

@GET请求可以设置@Query和@QueryMap参数。

如果一个key可以对应多个value,则参数类型可以设为List<String>来实现。如果某个参数是可选的,则调用的时候可以传递null进去,Retrofit就会跳过这个参数。注意参数类型如果是基本数据类型,则不能使用null,需要定义为包装类才能使用null达到跳过这个参数的目的。
Query参数也可以在interceptor当中全局添加:

OkHttpClient.Builder httpClient =      new OkHttpClient.Builder();httpClient.addInterceptor(new Interceptor() {      @Override    public Response intercept(Chain chain) throws IOException {        Request original = chain.request();        HttpUrl originalHttpUrl = original.url();        HttpUrl url = originalHttpUrl.newBuilder()                .addQueryParameter("apikey", "your-actual-api-key")                .build();        // Request customization: add request headers        Request.Builder requestBuilder = original.newBuilder()                .url(url);        Request request = requestBuilder.build();        return chain.proceed(request);    }});

如果有多个参数可以使用@QueryMap传递:

public interface NewsService() {      @GET("/news")    Call<List<News>> getNews(        @QueryMap Map<String, String> options    );}

@QueryMap也有个encoded字段表明是否已编码。

Post请求

表单请求会把请求的MIME类型设为application/x-www-form-urlencoded,如下:

public interface TaskService {      @FormUrlEncoded    @POST("tasks")    Call<Task> createTask(@Field("title") String title);}

@FormUrlEncoded注解不能用在@GET上,因为表单请求是要向服务端发送数据的。

同一表单字段对应多个值可以使用List作为参数来实现:

public interface TaskService {      @FormUrlEncoded    @POST("tasks")    Call<List<Task>> createTasks(@Field("title") List<String> titles);}

比如进行如下请求的话:

List<String> titles = new ArrayList<>();  titles.add("Research Retrofit");  titles.add("Retrofit Form Encoded")service.createTasks(titles); 

实际生成的表单数据是这样子的:

title=Research+Retrofit&title=Retrofit+Form+Encoded  

@Field注解可传入encoded字段表示该参数是否已经进行Url编码了。默认是false:

@Field(value = "title", encoded = true) String title

对于多个参数可以使用@FieldMap:

public interface UserService {      @FormUrlEncoded    @PUT("user")    Call<User> update(@FieldMap Map<String, String> fields);}

@FieldMap同样有个可选字段encoded来设置参数是否已经是Url编码过的了。

如果多个Post请求都有固定的字段,则可以封装成Java对象,简化请求步骤。
比如一个feeback接口接收以下几个参数,当中只有message是会变的,其他都是固定的:

@FormUrlEncoded@POST("/feedback")Call<ResponseBody> sendFeedbackSimple(      @Field("osName") String osName,    @Field("osVersion") int osVersion,    @Field("device") String device,    @Field("message") String message,    @Field("userIsATalker") Boolean userIsATalker);

那我们就可以把请求字段封装起来,不变的字段默认进行初始化:

public class UserFeedback {    private String osName = "Android";    private int osVersion = android.os.Build.VERSION.SDK_INT;    private String device = Build.MODEL;    private String message;    private boolean userIsATalker;    public UserFeedback(String message) {        this.message = message;        this.userIsATalker = (message.length() > 200);    }    // getters & setters    // ...}

新的请求方法:

@POST("/feedback")Call<ResponseBody> sendFeedbackConstant(@Body UserFeedback feedbackObject);  

进行请求的话就很简便了:

private void sendFeedbackFormAdvanced(@NonNull String message) {      FeedbackService taskService = ServiceGenerator.create(FeedbackService.class);    Call<ResponseBody> call = taskService.sendFeedbackConstant(new UserFeedback(message));    call.enqueue(new Callback<ResponseBody>() {        ...    });}

3. @FormUrlEncoded和@Query的区别:

@FormUrlEncoded用于Post请求,发送数据到服务器,数据存放在请求体当中。
@Query用于Get请求,用于向服务器请求数据。

Call的相关说明

可以调用Call对象的cancel()方法来取消请求,cancel()方法会触发onFailure(Call<ResponseBody> call, Throwable t)回调,可以调用call.isCanceled()判断失败原因是否是取消。

Call对象只能执行一次execute()或者enqueue(),如果要重复请求,可以调用call.clone()来创建副本进行请求。

Call.request()可以返回这个Call对应的request,注意如果这个request还没执行的话为了获取这个request可能会阻塞住线程。

上传文件

使用Retrofit2上传文件需要把文件封装在OkHttp的RequestBody或者MultipartBody.Part当中。
接口定义:

public interface FileUploadService {      @Multipart    @POST("upload")    Call<ResponseBody> upload(        @Part("description") RequestBody description,        @Part MultipartBody.Part file    );}

上传文件方法需要使用@Multipart注解。@Part注解表明该参数是一个multi-part请求的一部分,需要提供@Part自带参数用来表示该part的名称。当@Part用来注解MultipartBody.Part时比较特殊,无需提供自带参数,可省略。
上传文件的执行:

private void uploadFile(Uri fileUri) {      // create upload service client    FileUploadService service =            ServiceGenerator.createService(FileUploadService.class);    // 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);    // 创建文件的RequestBody    RequestBody requestFile = RequestBody.create(MediaType.parse(getContentResolver().getType(fileUri)),file);    // 使用文件RequestBody创建MultipartBody.Part,指定名称    MultipartBody.Part body =            MultipartBody.Part.createFormData("picture", file.getName(), requestFile);    // 添加另外一个Part    String descriptionString = "hello, this is description speaking";    RequestBody description = RequestBody.create(        okhttp3.MultipartBody.FORM, descriptionString);    // finally, execute the request    Call<ResponseBody> call = service.upload(description, body);    call.enqueue(new Callback<ResponseBody>() {        @Override        public void onResponse(Call<ResponseBody> call,                               Response<ResponseBody> response) {            Log.v("Upload", "success");        }        @Override        public void onFailure(Call<ResponseBody> call, Throwable t) {            Log.e("Upload error:", t.getMessage());        }    });}
原创粉丝点击