请求中的常量,默认值和计算值

Adding a Request for a Feedback Function

假设你需要为你的程序添加一个反馈功能,反馈功能通常允许用户输入文本以及获取设备信息,后端接口要求传递以下数据:

URL: /feedback  
Method: POST  
Request Body Params (Required):

- osName=[String]
- osVersion=[Integer]
- device=[String]
- message=[String]
- userIsATalker=[boolean]

Response: 204 (Empty Response Body) 

前三个信息是发送反馈的用户的信息,第四个是反馈信息的内容,最后一个是标志。

Simple Approach

第一步,我们先将API转换为Retrofit的形式。

@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);

下一步,你要为你的表单提交按钮设置点击事件,用来收集信息,计算值病情传递数据到服务器。

private void sendFeedbackFormSimple(@NonNull String message) {  
    // create the service to make the call, see first Retrofit blog post
    FeedbackService taskService = ServiceGenerator.create(FeedbackService.class);

    // create flag if message is especially long
    boolean userIsATalker = (message.length() > 200);

    Call<ResponseBody> call = taskService.sendFeedbackSimple(
            "Android",
            android.os.Build.VERSION.SDK_INT,
            Build.MODEL,
            message,
            userIsATalker
    );

    call.enqueue(new Callback<ResponseBody>() {
        ...
    });
}

在上面的例子中,唯一改变的是用户发送的message,而其他的像OSName,osVersion,device是不会改变的。但是我们有更好的办法可以使表达更简洁。

Advanced Approach With Passing the Only True Variable

首先我们需要改变接口的声明方式,下面已经将请求转换为java对象了:

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

其中的参数时一个UserFeedBack的对象,如下:

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
    // ...
}

构造方法中仅传入了一个meeage,其他值都是自动获取或者是计算得到的,那么代码就可以简化为:

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>() {
        ...
    });
}

这样即使程序有多个请求,每次只需要传一个参数就可以了,其他的会在类中自动获得。

取消请求

现在我们在一个Activity中创建了一个新的请求,用来下载文件:

public class CallExampleActivity extends AppCompatActivity {

    public static final String TAG = "CallInstances";
    private Callback<ResponseBody> downloadCallback;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_file_download);

        FileDownloadService downloadService = 
            ServiceGenerator.create(FileDownloadService.class);

        String fileUrl = "http://futurestud.io/test.mp4";
        Call<ResponseBody> call = 
            downloadService.downloadFileWithDynamicUrlSync(fileUrl);
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Log.d(TAG, "request success");
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.e(TAG, "request failed");
            }
        };);
    }

    // other methods
    // ...
}

现在添加了一个放弃按钮来让用户可以中断请求或者不发起请求。

String fileUrl = "http://futurestud.io/test.mp4";  
Call<ResponseBody> call =  
    downloadService.downloadFileWithDynamicUrlSync(fileUrl);
call.enqueue(new Callback<ResponseBody>() {  
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        Log.d(TAG, "request success");
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        Log.e(TAG, "request failed");
    }
});
    }

// something happened, for example: user clicked cancel button
call.cancel();  
}

Check If Request Was Cancelled

如果取消了请求,Retrofit会回调到onFailure()方法中。这个回调方法通常也用于在没有网络连接或者网络错误的时候。在应用程序中一般会用户会希望知道到底是哪种情况发生,在Retrofit可以使用call的isCanceled()方法来检查是否调用了取消请求。

new Callback<ResponseBody>() {  
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Log.d(TAG, "request success");
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                if (call.isCanceled()) {
                    Log.e(TAG, "request was cancelled");
                }
                else {
                    Log.e(TAG, "other larger issue, i.e. no network connection?");
                }

            }
        };

统计和复用请求

Reuse of Call Objects

关于Call和它的实例你必须知道:每个实例只能发起一次请求,你不能简单的使用同一个call来发起两次或者多次请求。

FileDownloadService downloadService = ServiceGenerator.create(FileDownloadService.class);

Call<ResponseBody> originalCall = downloadService.downloadFileWithDynamicUrlSync(fileUrl);  
Callback<ResponseBody> downloadCallback = new Callback<ResponseBody>() {...};

// correct usage:
originalCall.enqueue(downloadCallback);

// some other actions in between
// ...

// incorrect reuse:
// if you need to make the same request again, don't use the same originalCall again!
// it'll crash the app with a java.lang.IllegalStateException: Already executed.
originalCall.enqueue(downloadCallback); // <-- would crash the app  

加入你想多次使用一个call,你可以使用调用call的clone()方法来生成一个副本。你可以使用这个副本来请求服务器。

FileDownloadService downloadService =  
    ServiceGenerator.create(FileDownloadService.class);

Call<ResponseBody> originalCall =  
    downloadService.downloadFileWithDynamicUrlSync(fileUrl);
Callback<ResponseBody> downloadCallback = new Callback<ResponseBody>() {...};

// correct reuse:
Call<ResponseBody> newCall = originalCall.clone();  
newCall.enqueue(downloadCallback);  

Analyzing Requests With the Call Object

在Retrofit的每个回调方法中,无论请求成功还是失败都包含一个Call实例,这个Call实例就是你的原本的请求。但是这个Call实例不是让你重用(请使用clone()方法)的,是让你分析你的请求的。

FileDownloadService downloadService =  
    ServiceGenerator.create(FileDownloadService.class);

Call<ResponseBody> originalCall =  
    downloadService.downloadFileWithDynamicUrlSync(fileUrl);

originalCall.enqueue(new Callback<ResponseBody>() {  
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        checkRequestContent(call.request());
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        checkRequestContent(call.request());
    }
});

checkRequestContent()是用来拆解你的Call的。

private void checkRequestContent(Request request) {  
    Headers requestHeaders = request.headers();
    RequestBody requestBody = request.body();
    HttpUrl requestUrl = request.url();

    // todo make decision depending on request content
}

这个方法在发送给服务器后你想要查看你的请求的时候有用。

Preview Requests

调用call.request()方法,即使发出请求,照样会生成请求。

Note:如果请求尚未执行,call.request()方法会执行一些重要的计算。 不建议在Android的UI /主线程上调用.request()预览数据!

可选的路径参数

Optional Path Parameter

现在的API允许你添加筛选在/tasks路径后边添加taskid,/tasks/,那我们可以使用下面的方法来请求task列表

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

在上边的代码中为getTasks()方法添加了一个taskId参数,Retrofit将会正确的映射到路径上。

现在你需要传递一个空的值到方法中,如下面

# will be handled the same
https://your.api.url/tasks  
https://your.api.url/tasks/  

下面的代码演示了如何传空值得到结果

// request the list of tasks
TaskService service =  
    ServiceGenerator.createService(TaskService.class);
Call<List<Task>> voidCall = service.getTasks("");

List<Task> tasks = voidCall.execute().body();  

下面的代码演示乐值请求一个结果

// request a single task item
TaskService service =  
    ServiceGenerator.createService(TaskService.class);
Call<List<Task>> voidCall = service.getTasks("task-id-1234");

// list of tasks with just one item
List<Task> task = voidCall.execute().body();  

Attention

在实际使用中有时候会遇到这种情况:动态路径参数在中间,如下面:

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

请求的地址将变为: https://your.api.url/tasks//subtasks 然而Retrofit并不会正确处理这种请求,所以最好不要使用null作为参数值。

在请求体中加入纯文本

Solution 1: Scalars Converter

有多个现有的Retrofit转换器用于各种数据格式。将Java对象序列化和反序列化为JSON或XML或任何其他数据格式,反之亦然。 在可用的转换器中,Retrofit Scalars Converter,它可以解析任何要在请求体中放置的纯文本。转换同时适用于两个方向:请求和响应。

Scalars Converter可将请求内容以text / plain方式序列化。

Add Scalars Converter to Your Project

在你的gradle中添加如下代码: compile 'com.squareup.retrofit2:converter-scalars:2.1.0'

将Scalars Converter添加到Retrofit实例。

Note:添加转换器的顺序很重要,经验告诉我们,将Gson转化器作为最后一个转换器添加到Retrofit实例中。

Retrofit retrofit = new Retrofit.Builder()  
        .addConverterFactory(ScalarsConverterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl("https://your.base.url/")
        .build();

Use Primitives and Boxed Types for Requests & Response

下面的代码段中显示的仅仅是发送和接收文本值,只使用Gson转换器定义的字符串将不会正确地映射数据,并在运行时期间发生错误。

public interface ScalarService {  
    @POST("path")
    Call<String> getStringScalar(@Body String body);
}

使用Scalars Convert将会将你的字符串添加到你的请求体中,Retrofit将会挑选第一个合适的转换器来转换。

String body = "plain text request body";  
Call<String> call = service.getStringScalar(body);

Response<String> response = call.execute();  
String value = response.body();  

这样,我们传递的字符串就会正确的发送到服务器了。

Solution 2: Use RequestBody Class

public interface ScalarService {  
    @POST("path")
    Call<ResponseBody> getStringRequestBody(@Body RequestBody body);
}

ResponseBody允许我们接受任何对象,下面的代码演示了使用RequestBody和ResponseBody

String text = "plain text request body";  
RequestBody body =  
        RequestBody.create(MediaType.parse("text/plain"), text);

Call<ResponseBody> call = service.getStringRequestBody(body);  
Response<ResponseBody> response = call.execute();  
String value = response.body().string();  

传入的参数时通过RequestBody.create()方法创建的。