Retrofit 2.0 详解(一)基本用法

什么是 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/posthttp://localhost:8080/post
http://localhost:8080/RetrofitService/NewsServletposthttp://localhost:8080/RetrofitService/NewsServlet/post
http://localhost:8080/RetrofitService/NewsServlethttp://www.jianshu.com/http://www.jianshu.com/

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

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

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

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

Retrofit提供的Converter

ConverterGradle依赖
Gsoncom.squareup.retrofit2:converter-gson:2.0.2
Jacksoncom.squareup.retrofit2:converter-jackson:2.0.2
Moshicom.squareup.retrofit2:converter-moshi:2.0.2
Protobufcom.squareup.retrofit2:converter-protobuf:2.0.2
Wirecom.squareup.retrofit2:converter-wire:2.0.2
Simple XMLcom.squareup.retrofit2:converter-simplexml:2.0.2
Scalarscom.squareup.retrofit2:converter-scalars:2.0.2

Retrofit提供的CallAdapter

CallAdapterGradle依赖
guavacom.squareup.retrofit2:adapter-guava:2.0.2
Java8com.squareup.retrofit2:adapter-java8:2.0.2
rxjavacom.squareup.retrofit2:adapter-rxjava:2.0.2

结语

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



作者:越努力越幸运阳
链接:https://www.jianshu.com/p/af8bd1abda82
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值