请求中的常量,默认值和计算值
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-id>,那我们可以使用下面的方法来请求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()方法创建的。
网友评论
能从原理上讲讲为什么要按照这个顺序吗?