美文网首页Rx系列androidAndroid
Retrofit 2.0 详解(一)基本用法

Retrofit 2.0 详解(一)基本用法

作者: 越努力越幸运阳 | 来源:发表于2017-07-03 18:21 被阅读378次

    什么是 Retrofit ?

    Retrofit是Square开发的一个Android和Java的REST客户端库。这个库非常简单并且具有很多特性,相比其他的网络库,更容易让初学者快速掌握。

    Retrofit配置

    在 app/build.gradle 添加依赖
    在这里我们最好查看一下retrofit的官网添加最新依赖。

    compile 'com.squareup.retrofit2:retrofit:2.2.0'
    

    创建retrofit实例

    String baseUrl = "http://192.168.1.8:8080/RetrofitService/";
    Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .build();
    

    创建Retrofit实例时需要通过Retrofit.Builder,并调用baseUrl方法设置URL。
    注: Retrofit2 的baseUlr 必须以 /(斜线) 结束,不然会抛出一个IllegalArgumentException,所以如果你看到别的教程没有以 / 结束。

    接口定义

    以获取指定id的News为例:

    public interface NewsService {
        //普通call方式
        @GET("NewsServlet")
        Call<ResponseBody> getNews(@Query("id") int id);
    }
    

    注意,这里是interface不是class,所以我们是无法直接调用该方法,我们需要用Retrofit创建一个NewsService的代理对象。

    NewsService newsService = createRetrofit().create(NewsService.class);
    

    拿到代理对象之后,就可以调用该方法啦。

    接口调用

     Call<ResponseBody> answers = newsService.getNews(1);
            answers.enqueue(new Callback<ResponseBody>() {
                @Override
                public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                    try {
                        Log(response.body().string());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                @Override
                public void onFailure(Call<ResponseBody> call, Throwable t) {
                    t.printStackTrace();
                }
            });
    

    Retrofit注解详解

    上面提到Retrofit 共22个注解,这节就专门介绍这22个注解,为帮助大家更好理解我将这22个注解分为三类,并用表格的形式展现出来,表格上说得并不完整,具体的见源码上的例子注释

    第一类:HTTP请求方法
    HTTP请求方法注解.png

    以上表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:methodpathhasBody下面是用HTTP注解实现上面的例子。

         /**
         * method 表示请求的方法,区分大小写
         * path表示路径
         * hasBody表示是否有请求体
         */
        @HTTP(method = "GET", path = "NewsServlet", hasBody = false)
        Call<ResponseBody> getNews1(@Query("id") int id);
    

    注:method 的值 retrofit 不会做处理,所以要自行保证其准确性,之前使用小写也可以是因为示例源码中的服务器不区分大小写,所以希望大家注意。

    第二类:标记类
    标记类注解.png

    FormUrlEncoded注解接口的2种写法

        /**
         * {@link FormUrlEncoded} 表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
         * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
         */
        @POST("/NewsServlet")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded1(@Field("name") String name, @Field("age") int age);
        /**
         * Map的key作为表单的键
         */
        @POST("/NewsServlet")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
    

    分别对应调用的2种方法

    第一种:

    NewsService newsService = createRetrofit().create(NewsService.class);
    Call<ResponseBody> answers = newsService.testFormUrlEncoded1("txy", 18);
    
    

    第二种:

    NewsService newsService = createRetrofit().create(NewsService.class);
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "txy");
    map.put("age", 18);
    Call<ResponseBody> answers = newsService.testFormUrlEncoded2(map);
    

    Multipart注解接口的3种写法,Multipart一般是上传文件的时候使用

        /**
         * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
         * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
         */
        @POST("NewsServlet")
        @Multipart
        Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
    
        /**
         * PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
         * 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
         * 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
         */
        @POST("NewsServlet")
        @Multipart
        Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);
    
        @POST("NewsServlet")
        @Multipart
        Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
    

    对应的3种调用方式

    第一种:

            NewsService newsService = createRetrofit().create(NewsService.class);
            MediaType textType = MediaType.parse("text/plain");
            RequestBody name = RequestBody.create(textType, "txy");
            RequestBody age = RequestBody.create(textType, "18");
            //构建要上传的文件
            File file = new File(Environment.getExternalStorageDirectory(), "paoche.jpg");
            RequestBody requestFile =
                    RequestBody.create(MediaType.parse("application/otcet-stream"), file);
            MultipartBody.Part filePart =
                    MultipartBody.Part.createFormData("fileUploader", file.getName(), requestFile);
            Call<ResponseBody> answers = newsService.testFileUpload1(name, age, filePart);
    
    

    第二种:

            NewsService newsService = createRetrofit().create(NewsService.class);
            MediaType textType = MediaType.parse("text/plain");
            RequestBody name = RequestBody.create(textType, "txy");
            RequestBody age = RequestBody.create(textType, "18");
            Map<String, RequestBody> fileUpload2Args = new HashMap<>();
            fileUpload2Args.put("name", name);
            fileUpload2Args.put("age", age);
            //构建要上传的文件
            File file = new File(Environment.getExternalStorageDirectory(), "paoche1.jpg");
            RequestBody requestFile =
                    RequestBody.create(MediaType.parse("application/otcet-stream"), file);
            MultipartBody.Part filePart =
                    MultipartBody.Part.createFormData("fileUploader", file.getName(), requestFile);
            Call<ResponseBody> answers = newsService.testFileUpload2(fileUpload2Args, filePart);
    
    

    第二种:

            NewsService newsService = createRetrofit().create(NewsService.class);
            MediaType textType = MediaType.parse("text/plain");
            RequestBody name = RequestBody.create(textType, "txy");
            RequestBody age = RequestBody.create(textType, "18");
            Map<String, RequestBody> fileUpload3Args = new HashMap<>();
            fileUpload3Args.put("name", name);
            fileUpload3Args.put("age", age);
            //构建要上传的文件
            File file = new File(Environment.getExternalStorageDirectory(), "paoche1.jpg");
            RequestBody requestFile =
                    RequestBody.create(MediaType.parse("application/otcet-stream"), file);
            fileUpload3Args.put("fileUploader\"; filename=\"paoche3.jpg",requestFile);
            Call<ResponseBody> answers = newsService.testFileUpload3(fileUpload3Args);
    
    
    第三类:参数类
    参数类注解.png

    注1:{占位符}和PATH尽量只用在URL的path部分,url中的参数使用QueryQueryMap 代替,保证接口定义的简洁
    注2: QueryFieldPart这三者都支持数组和实现了Iterable接口的类型,如ListSet等,方便向后台传递数组。

    Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
    //结果:ids[]=0&ids[]=1&ids[]=2
    

    FieldFieldMapPartPartMap 前面的例子已经讲过。
    下面说一下HeaderHeaders的二种用法、静态和动态我自己理解的,看下接口怎么写
    第一种静态方式:

        //静态添加Header
        @Headers("Cache-Control: max-age=640000")
        @GET("NewsServlet")
        Call<ResponseBody> testHeader1();
        //静态添加多个Header
        @Headers({"X-Foo: Bar","X-Ping: Pong"})
        @GET("NewsServlet")
        Call<ResponseBody> testHeader2();
    

    第二种动态方式:

        //动态添加Header
        @GET("NewsServlet")
        Call<ResponseBody> testHeader3(@Header("Cache-Control") int header, @Query("name") String name);
        //动态添加Header
        @GET("NewsServlet")
        Call<ResponseBody> testHeader4(@HeaderMap Map<String, String> headers, @Query("name") String name);
    

    Gson与Converter

    在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody,这也是什么我在前面的例子接口的返回值都是 Call<ResponseBody>,但如果响应体只是支持转换为ResponseBody的话何必要引用泛型呢,返回值直接用一个Call就行了嘛,既然支持泛型,那说明泛型参数可以是其它类型的,而Converter就是Retrofit为我们提供用于ResponseBody转换为我们想要的类型,有了Converter之后我们就可以写把我们的第一个例子的接口写成这个样子了:

    public interface NewsService {
        //普通call方式
        @GET("NewsServlet")
        Call<News> getNews(@Query("id") int id);
    }
    

    当然只改变泛型的类型是不行的,我们在创建Retrofit时需要明确告知用于将ResponseBody转换我们泛型中的类型时需要使用的Converter

    引入Gson支持:

    compile 'com.squareup.retrofit2:converter-gson:2.0.1'
    

    通过GsonConverterFactory为Retrofit添加Gson支持:

    Gson gson = new GsonBuilder()
            //配置你的Gson
            .setDateFormat("yyyy-MM-dd hh:mm:ss")
            .create();
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(baseUrl)
            //可以接收自定义的Gson,当然也可以不传
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build();
    

    说道Gson我们就先聊一下有些Http服务返回一个固定格式的数据的问题。 例如:

    {
     "code": 0,
     "message": "成功",
     "data": {}
    }
    

    或:

    {
     "code": 0,
     "message": "成功",
     "data": []
    }
    

    不一样的地方就是data是一个对象或对象数组,这种情况下咱们怎么统一处理,我们可以创建一个BaseModel类

    public class BaseModel<T> {
        public int resultCode;
        public String resultMessage;
        public  T data;
    }
    

    以demo返回的2种数据为例
    data是普通对象

    {"code":200,"data":{"comments":[{"content":"我是内容"}],"date":"20170603","id":1,"likes":"我喜欢新闻","title":"我是标题","views":"我是views"},"message":"获取成功!"}
    

    以第一个例子为例咱们看下怎么修改,Call的泛型填写BaseModel<News>即可

    public interface NewsService {
        //普通call方式
        @GET("NewsServlet")
        Call<BaseModel<News>> testConverter1(@Query("id") int id);
    }
    

    data是普通对象数组

    {"code":200,"data":[{"comments":[{"content":"我是内容"}],"date":"20170603","id":1,"likes":"我喜欢新闻","title":"我是标题","views":"我是views"}],"message":"获取成功!"}
    

    Call的泛型填写BaseModel<List<News>>即可

    public interface NewsService {
        //普通call方式
        @POST("NewsServlet")
        Call<BaseModel<List<News>>> testConverter1();
    }
    

    结合我们现在用的只传JSON的请求,请求headercontent-typeapplication/json,也就是@Body的用法写一个列子:

    @POST("NewsServlet")
    Call<BaseModel<List<News>>> testConverte3(@Body News news);
    

    @Body注解的的Blog将会被Gson转换成RequestBody发送到服务器。
    下面咱们看下接口怎么调用

    NewsService newsService = createRetrofit().create(NewsService.class);
    News news = new News();
    news.likes = "likes";
    news.date = "20170619";
    news.title = "Converter用法3";
    news.id = 1;
    news.views = "views";
    Call<BaseModel<List<News>>> call = newsService.testConverter3(news);
    

    服务器收到的数据就是如下这样

    {"comments":[],"date":"20170619","id":1,"likes":"likes","title":"Converter用法3","views":"views"}
    

    RxJava与CallAdapter

    说到Retrofit就不得说到另一个火到不行的库RxJava,网上已经不少文章讲如何与Retrofit结合,但这里还是会有一个RxJava的例子,不过这里主要目的是介绍使用CallAdapter所带来的效果。

    注意:下面说的RxJava是指的RxJava2.0

    第3节介绍的Converter是对于Call<T>T的转换,而CallAdapter则可以对Call转换,这样的话Call<T>中的Call也是可以被替换的,而返回值的类型就决定你后续的处理程序逻辑,同样Retrofit提供了多个CallAdapter,这里以RxJava的为例,用Observable代替Call

    引入RxJava支持:

    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
    // 针对rxjava2.x(adapter-rxjava2的版本要 >= 2.2.0)
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    

    通过RxJavaCallAdapterFactory为Retrofit添加RxJava支持:

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(baseUrl)
        //可以接收自定义的Gson,当然也可以不传
        .addConverterFactory(GsonConverterFactory.create(gson))
        // 针对rxjava2.x
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build();
    

    接口设计:

    public interface NewsService {
        @GET("NewsServlet")
        Observable<BaseModel<List<News>>> testCallAdapter();
    }
    

    使用:

    NewsService newsService = createRetrofit().create(NewsService.class);
    Observable<BaseModel<List<News>>> observable = newsService.testCallAdapter();
    observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<BaseModel<List<News>>>() {
                @Override
                public void onSubscribe(Disposable d) {
                    //运行在io线程
                }
    
                @Override
                public void onNext(BaseModel<List<News>> value) {
                    Log("RXJava用法 onNext:" + FastJsonUtils.toJson(value));
                }
    
                @Override
                public void onError(Throwable e) {
                    Log("RXJava用法 onError:" + e.getMessage());
                }
    
                @Override
                public void onComplete() {
                    Log("RXJava用法 onComplete");
                }
            });
    

    结果:

    {"code":200,"data":[{"comments":[{"content":"我是内容"}],"date":"20170603","id":1,"likes":"我喜欢新闻","title":"我是标题","views":"我是views"}],"success":true}
    

    有的时候我们需要获取原始的json的字符串,GSON是满足不了我们的,下面我们来看下怎么获取原始的json串,我们需要借助另一个Converter,后面也会介绍都有哪些Converter

    引入scalars支持:

    compile 'com.squareup.retrofit2:converter-scalars:2.0.2'
    

    通过ScalarsConverterFactoryRetrofit添加String支持:

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(baseUrl)
        //添加ScalarsConverterFactory支持
        .addConverterFactory(ScalarsConverterFactory.create())
        //可以接收自定义的Gson,当然也可以不传
        .addConverterFactory(GsonConverterFactory.create(gson))
         // 针对rxjava2.x
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build();
    

    注意: 如果需要既支持String又支持Gson需要先设置ScalarsConverterFactory 后设置 GsonConverterFactory 如果上所示。

    接口

    @GET("NewsServlet")
    Observable<String> testScalars();
    

    看下怎么调用

    Observable<String> observable = newsService.testScalars();
    observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<String>() {
                @Override
                public void onSubscribe(Disposable d) {
                }
                @Override
                public void onNext(String value) {
                    Log("Scalar用法 onNext value:" + value);
                }
    
                @Override
                public void onError(Throwable e) {
                    Log("Scalar用法 onError " +e.getMessage());
                }
                @Override
                public void onComplete() {
                }
            });
    
    

    像上面的这2种情况最后我们无法获取到返回的Header和响应码的,如果我们需要这两者,提供两种方案:
    1、用Observable<Response<T>> Observable<T> ,这里的Responseretrofit2.Response
    2、用Observable<Result<T>>代替Observable<T>,这里的Result是指retrofit2.adapter.rxjava.Result,这个Result中包含了Response的实例

    看下第一种接口怎么设计

    @GET("NewsServlet")
    Observable<Response<BaseModel<List<News>>>> testRxAndroid2();
    

    可以理解在以前BaseModel<List<News>>的外面把包装一层Response最终Response<BaseModel<List<News>>>,接下来看下怎么调用

    Observable<Response<BaseModel<List<News>>>> observable = newsService.testRxAndroid2();        observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<Response<BaseModel<List<News>>>>() {
                @Override
                public void onSubscribe(Disposable d) {
                }
                @Override
                public void onNext(Response<BaseModel<List<News>>> value) {
                    Headers headers = value.headers();
                    Set<String> names = headers.names();
                    Log("RXJava用法2 onNext value:" + FastJsonUtils.toJson(value.body()));
                    Log("RXJava用法2 onNext headers:" + FastJsonUtils.toJson(names));
                    BaseModel<List<News>> body = value.body();
                    Log("RXJava用法2 onNext body:" + FastJsonUtils.toJson(body));
                }
                @Override
                public void onError(Throwable e) {
                    Log("RXJava用法2 onError:"+e.getMessage());
                }
                @Override
                public void onComplete() {
                    Log("RXJava用法2 onComplete");
                }
            });
    

    通过Responseheaders()方法获取所有header

    其它说明

    Retrofit.Builder

    前面用到了 Retrofit.Builder 中的baseUrladdCallAdapterFactoryaddConverterFactorybuild方法,还有callbackExecutorcallFactoryclientvalidateEagerly这四个方法没有用到,这里简单的介绍一下。

    | 方法 | 用途 |
    | ------------- |-------------| -----|
    | callbackExecutor(Executor) | 指定Call.enqueue时使用的Executor,所以该设置只对返回值为Call的方法有效 |
    | callFactory(Factory) | 设置一个自定义的okhttp3.Call.Factory,那什么是Factory呢?OkHttpClient就实现了okhttp3.Call.Factory接口,下面的client(OkHttpClient)最终也是调用了该方法,也就是说两者不能共用|
    | client(OkHttpClient) | 设置自定义的OkHttpClient,以前的Retrofit版本中不同的Retrofit对象共用同OkHttpClient,在2.0各对象各自持有不同的OkHttpClient实例,所以当你需要共用OkHttpClient或需要自定义时则可以使用该方法,如:处理Cookie、使用stetho 调式等 |
    | validateEagerly(boolean) | 是否在调用create(Class)时检测接口定义是否正确,而不是在调用方法才检测,适合在开发、测试时使用 |

    Retrofit的Url组合规则
    BaseUrl 和URL有关的注解中提供的值 最后结果
    http://localhost:8080/RetrofitService/NewsServlet /post http://localhost:8080/post
    http://localhost:8080/RetrofitService/NewsServlet post http://localhost:8080/RetrofitService/NewsServlet/post
    http://localhost:8080/RetrofitService/NewsServlet http://www.jianshu.com/ http://www.jianshu.com/

    从上面不能难看出以下规则:

    • 如果你在注解中提供的url是完整的url,则url将作为请求的url。

    • 如果你在注解中提供的url是不完整的url,且不以 / 开头,则请求的url为baseUrl+注解中提供的值

    • 如果你在注解中提供的url是不完整的url,且以 / 开头,则请求的url为baseUrl的主机部分+注解中提供的值

    Retrofit提供的Converter
    Converter Gradle依赖
    Gson com.squareup.retrofit2:converter-gson:2.0.2
    Jackson com.squareup.retrofit2:converter-jackson:2.0.2
    Moshi com.squareup.retrofit2:converter-moshi:2.0.2
    Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
    Wire com.squareup.retrofit2:converter-wire:2.0.2
    Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
    Scalars com.squareup.retrofit2:converter-scalars:2.0.2
    Retrofit提供的CallAdapter
    CallAdapter Gradle依赖
    guava com.squareup.retrofit2:adapter-guava:2.0.2
    Java8 com.squareup.retrofit2:adapter-java8:2.0.2
    rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2

    结语

    本篇博客已经完成,后续我还会出进阶版本,包括调用https请求、下载文件、报文加密等等。最后感谢 怪盗kidou 老铁,本篇博客好多都是借鉴的这位兄弟的,添加了一些自己的理解。

    相关文章

      网友评论

      本文标题:Retrofit 2.0 详解(一)基本用法

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