Retrofit使用详解(三)

作者: sakasa | 来源:发表于2017-02-14 10:18 被阅读433次

    OKHttp(一)之Calls

    OKHttp(二)之Connections

    OkHttp(三)之使用方法

    OkHttp(四)之拦截器

    OkHttp(五)之HTTPS

    Retrofit使用详解(一)

    Retrofit使用详解(二)

    Retrofit使用详解(三)

    Retrofit使用详解(四)

    Retrofit使用详解(五)

    Retrofit使用详解(六)

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

    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()方法创建的。

    相关文章

      网友评论

      • mundane:“添加转换器的顺序很重要,经验告诉我们,将Gson转化器作为最后一个转换器添加到Retrofit实例中。”
        能从原理上讲讲为什么要按照这个顺序吗?

      本文标题:Retrofit使用详解(三)

      本文链接:https://www.haomeiwen.com/subject/milxwttx.html