Retrofit2整理

简介

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 Practice
base url: https://futurestud.io/api/  
endpoint: my/endpoint  
Result:   https://futurestud.io/api/my/endpoint

# Bad Practice
base url: https://futurestud.io/api  
endpoint: /my/endpoint  
Result:   https://futurestud.io/my/endpoint

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

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

# Example 2
base 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 url
base url: http://futurestud.io/api/  
endpoint: https://api.futurestud.io/  
Result:   https://api.futurestud.io/

# Example 4 — Keep the base url’s scheme
base url: https://futurestud.io/api/  
endpoint: //api.futurestud.io/  
Result:   https://api.futurestud.io/

# Example 5 — Keep the base url’s scheme
base 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());
        }
    });
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值