Retrofit2讲解

前言:

本文使用的网络请求API:

get请求:“https://devapi.qweather.com/v7/weather/now?location=101010100&key=a5cf6ab782a14013b08fb92a57dd2f72”

此为和风天气的一个接口, 直接用浏览器登录查看返回json数据。

post请求:https://v2.alapi.cn/api/dog

此为ALAPI网站提供学者练习使用的一个API

一. 概述:

在这里插入图片描述
注意:

  • 准却来说Retrofit是一个RESTful的HTTP网络请求的封装
  • 原因:网络请求的工作本质上是Okhttp完成,而Retrofit仅负责网络请求接口的封装

在这里插入图片描述

  • App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作
  • 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析

Retrofit的优点:

  • 超级解耦 ,接口定义、接口参数、接口回调不在耦合在一起
  • 可以配置不同的httpClient来实现网络请求,如okhttp、httpclient
  • 支持同步、异步、Rxjava
  • 可以配置不同反序列化工具类来解析不同的数据,如json、xml
  • 请求速度快,使用方便灵活简洁

二:注解

Retrofit使用大量注解来简化请求,Retrofit将okhttp请求抽象成java接口,使用注解来配置和描述网络请求参数。大概可以分为以下几类,我们先来看看各个注解的含义,再一一去实践解释。

1. 请求方法注解
请求方法注解说明
@GETget请求
@POSTpost请求
@PUTput请求
@DELETEdelete请求
@PATCHpatch请求,该请求是对put请求的补充,用于更新局部资源
@HEADhead请求
@OPTIONSoptions请求
@HTTP通过注解,可以替换以上所有的注解,它拥有三个属性:method、path、hasBody
2. 请求头注解
请求头注解说明
@Headers用于添加固定请求头,可以同时添加多个,通过该注解的请求头不会相互覆盖,而是共同存在
@Header作为方法的参数传入,用于添加不固定的header,它会更新已有请求头
3.请求参数注解
请求参数注解说明
@Body多用于Post请求发送非表单数据,根据转换方式将实例对象转化为对应字符串传递参数,比如使用Post发送Json数据,添加GsonConverterFactory则是将body转化为json字符串进行传递
@Filed多用于Post方式传递参数,需要结合@FromUrlEncoded使用,即以表单的形式传递参数
@FiledMap多用于Post请求中的表单字段,需要结合@FromUrlEncoded使用(当存在多个表单时使用此注解,减少方法中参数的个数)
@Part用于表单字段,Part和PartMap与@multipart注解结合使用,适合文件上传的情况
@Query用于Get请求中的参数
@PartMap用于表单字段,默认接受类型是Map<String,RequestBody>,可用于实现多文件上传
@Path用于Url中的占位符
@QueryMap与Query类似,用于不确定表单参数
4.请求和响应格式(标记)注解

解释:此注解添加至方法前声明此请求有发送表单,上传文件或下载大文件等动作。

标记类注解说明
@FromUrlCoded表示请求发送编码表单数据,每个键值对需要使用@Filed注解
@Multipart表示请求发送form_encoded数据(使用于有文件上传的场景),每个键值对需要用@Part来注解键名,随后的对象需要提供值
@Streaming表示响应用字节流的形式返回,如果没有使用注解,默认会把数据全部载入到内存中,该注解在下载大文件时特别有用

三:注解详解

  1. 概述:我们先来解释注解的使用和需要注意的问题(具体的使用步骤后面会给出)。上面提到注解是用来配置和描述网络请求参数的,我们来逐个讲解一下,首先创建网络接口类:

    • Retrofit将okhttp请求抽象成java接口,采用注解描述和配置网络请求参数,用动态代理将该接口的注解“翻译”成一个Http请求,最后执行Http请求。
    • 注意:接口中的每个方法的参数都要用注解标记,否则会报错。

    简单来说,我们需要先创建一个用于网络请求的接口,通过为接口中定义的请求方法添加不同的注解实现不同的网络请求功能。

    如:创建网络请求接口ApiService

    public interface ApiService {
       
    }
    
1. @GET、@Query、@QueryMap的使用

此get请求使用前言中的get请求网址

  1. 无参的get请求
public interface ApiService {
    
    @GET("https://devapi.qweather.com/v7/weather/now?location=101010100&key=a5cf6ab782a14013b08fb92a57dd2f72")
    Call<*> getCall();
}
  • @GET 请求方法注解,get请求,括号内的是请求地址,Url的一部分
  • Call<*> 返回类型,*表示接收数据的类,一般自定义,下文展示本接口接受数据类型的类
  • getCall() 接口方法名称,括号内可以写入参数

此为网络接口最简单的一种形式,这是一个没有网络参数的get请求方式,需要在方法头部添加@GET注解,表示采用get方法访问网络请求,括号内的是请求的地址(Url的一部分,也可以是完整的Url,如上文) ,其中返回类型是Call<*>,*表示接收数据的类,如果想直接获取ResponseBody中的内容,可以定义网络请求返回值为Call,ResponseBody是请求网络后返回的原始数据,如果网络请求没有参数,不用写。

简单Url介绍,对于上Url,它是一个请求天气的接口,通过Url组成不同,实现get请求到的数据不同,正常实现不同Url方式为字符串拼接,如:
在这里插入图片描述
而Retrofit通过注解将Url分成两部分:一部分为在创建Retrofit实例时通过.baseUrl(”baseUrl“)设置,第二部分在网络接口注解中设置,如接口@get(“ApiUrl”),网络请求的完整地址Url = baseUrl + ApiUrl。

下面将上Url分割,分为baseUrl=“https://devapi.qweather.com/”,ApiUrl="v7/weather/now?location=101010100&key=a5cf6ab782a14013b08fb92a57dd2f72"两部分。

  1. 有参的get请求:

    public interface ApiService {
    	@GET("v7/weather/now")
        Call<Wth_now_out> getCall(@Query("location") String location,@Query("key") String key);
    }
    
    • @Query 请求参数注解,用于Get请求中的参数
    • “location”/“key” 参数字段,与后台给的字段需要一致
    • String 声明的参数类型
    • location/key 实际参数

    添加参数在方法括号内添加@Query,后面是参数类型和参数字段,表示后面location的取值作为"location"的值,key的取值作为"key"的值,其实就是键值对,Retrofit会把两个字段拼接到接口中,追加在"v7/weather/now"后面,此时若我们在方法点用时出入参数为(“101010100”, “a5cf6ab782a14013b08fb92a57dd2f72”),在retrofit实例设置baseUrl为"https://devapi.qweather.com/",那么拼接参数之后完整的请求参数就为"https://devapi.qweather.com/v7/weather/now?location=101010100&key=a5cf6ab782a14013b08fb92a57dd2f72"。

    有关@QueryMap的使用:

    对于上参数我们发现其存在两个Query参数,对于这种@query较多的情况我们就可以将其用一个@QueryMap注解代替:

    public interface ApiService {
    	@GET("v7/weather/now")
    	Call<Wth_now_out> getCall(@QueryMap Map<String, Object> map);
    }
    
    • @QueryMap 请求参数注解,与@Query类似,用于不确定表单参数
    • Map<String, Object> map 通过Map将不确定的参数传入,相当于多个Query参数

    此map参数设置在接口方法被使用时传入,如:

        Map<String ,Object> map = new HashMap<>();
        map.put("location","101010100");
        map.put("key","a5cf6ab782a14013b08fb92a57dd2f72");
        Call<Wth_now_out> call = apiService.getCall("now",map);
    

    其主要用于表单参数不确定的情况,即Url中?号之后存在的参数不确定。

2.@POST、@FormUrlEncoded、@File、@FileMap、@Body的使用

​ 此post请求使用前言中的post请求网址

​ 此post请求的baseUrl=“https://v2.alapi.cn/”

  1. 无参的post方法的请求:

    public interface ApiService {
        @POST("")
    	Call<ResponseBody> postCall();
    }
    
    • @POST 请求方法注解,表示采用post方法访问网络请求,括号后面是部分的URL地址

    post请求网络方法,需要在方法头部添加@POST注解,表示采用post方法访问网络请求.

  2. 有参的Post请求

    public interface ApiService {
        @FormUrlEncoded
        @POST("api/dog")
        Call<Object> postCall(@Field("token") String token);
    }
    
    
    • @FormUrlEncoded 请求格式注解,请求实体是一个From表单,每个键值对需要使用@Field注解
    • @Field 请求参数注解,提交请求的表单字段,必须要添加,而且需要配合@FormUrlEncoded使用
    • “token” 参数字段,与后台给的字段需要一致
    • String 声明的参数类型
    • token 实际参数,表示后面token的取值作为"token"的值

    Post请求如果有参数需要在头部添加@FormUrlEncoded注解,表示请求实体是一个From表单,每个键值对需要使用@Field注解,使用@Field添加参数,这是发送Post请求时,提交请求的表单字段,必须要添加的,而且需要配合@FormUrlEncoded使用,若为在头部添加@FormUrlEncoded注解,会抛出如下异常:
    在这里插入图片描述
    有关@FieldMap的使用:

    当有多个不确定参数时,我们可以使用@FieldMap注解,@FieldMap与@Field的作用一致,可以用于添加多个不确定的参数,类似@QueryMap,Map的key作为表单的键,Map的value作为表单的值。

    public interface ApiService {	
    	@FormUrlEncoded
        @POST("api/dog")
        Call<Object> postCall(@FieldMap Map<String,Object> map);
    }
    
        Map<String,Object> map1 = new HashMap<>();
        map1.put("token","fTN2p6btdPMrU2Lf");
        map1.put("format","json");
        Call<Object> post = apiService1.postCall(map1);
    
    • @FieldMap 请求参数注解,与@Field作用一致,用于不确定表单参数
    • Map<String, Object> map 通过Map将不确定的参数传入,相当于多个Field参数

    适用于Post请求的还有一个注解@Body,@Body可以传递自定义类型数据给服务器,多用于post请求发送非表单数据,比如用传递Json格式数据,它可以注解很多东西,比如HashMap、实体类等,我们来看看它用法:

    public interface ApiService {	
    	@POST("")
    	Call<Object> getPsotDataBody(@Body RequestBody body);
    }
    

    特别注意:@Body注解不能用于表单或者支持文件上传的表单的编码,即不能与@FormUrlEncoded和@Multipart注解同时使用

3.@Path

使用第一个get请求网址

public interface ApiService {	
    @GET("v7/weather/{time}")
    Call<Wth_now_out> getCall(@Path ("time") String time, @QueryMap Map<String, Object> map);
}
  • @QueryMap get请求方法参数的注解,上面已经解释了,这里就不重复讲
  • @Path 请求参数注解,用于Url中的占位符{},在网址中的参数

@Path注解用于Url中的占位符{},在网址中的参数,如上面 @GET(“v7/weather/{time}”)的time,通过{}占位符来标记time,使用@Path注解传入time的值,注意有的Url既有占位符又有"?"后面的键值对,其实@Query和@Path两者是可以共用的。在发起请求时,{time}会被替换为方法中第二个参数的值time。

    Map<String ,Object> map = new HashMap<>()Map<String ,Object> map = new HashMap<>();
    map.put("location","101010100");
    map.put("key","a5cf6ab782a14013b08fb92a57dd2f72");
    Call<Wth_now_out> call = apiService.getCall("now",map);

最终{time}会被替换为now,此时形成的牵绊部分Url为:https://devapi.qweather.com/v7/weather/now,剩余**?号**后面的Url由@Query传入。

4.@HTTP

@HTTP注解的作用是替换@GET、@POST、@PUT、@DELETE、@HEAD以及更多拓展功能

如可通过此注解代替@POST注解实现post请求,如下:

public interface ApiService {
    @FormUrlEncoded
    @POST("api/dog")
    Call<Object> postCall(@FieldMap Map<String,Object> map);

    @FormUrlEncoded
    @HTTP(method = "POST",path = "api/dog", hasBody = true)
    Call<Object> postCall1(@FieldMap Map<String,Object> map);

}
  • method 表示请求的方法,区分大小写,这里的值retrofit不会再做任何处理,必须要保证正确
  • path 网络请求地址路径
  • hasBody 是否有请求体,boolean类型

除请求接口部分的方法有变,别的部分与正常使用@POST请求一致,另外需要注意在使用@HTTP注解实现post请求时,若含有请求体,那么对应的标识注解@FormUrlEncoded一定要加上,若为别的请求体,那么就添加与之对应的标识注解。

@HTTP注解可以通过method字段灵活设置具体请求方法,通过path设置网络请求地址,用的比较少。使用方法与设置的请求方法种类一致。

5.@Url

如果需要重新地址接口地址,可以使用@Url,将地址以参数的形式传入即可。如果有@Url注解时,GET传入的Url必须省略。不然会抛出如下异常。
在这里插入图片描述
如此种形式:

public interface ApiService {
    @FormUrlEncoded
    @POST("api/dog")
    Call<Object> postCall(@Url String url,@FieldMap Map<String,Object> map);
}

正常应为:

public interface ApiService {
    @FormUrlEncoded
    @POST
    Call<Object> postCall(@Url String url,@FieldMap Map<String,Object> map);
}
  • @Url 表示指定请求路径,可以当做参数传入
6.@Streaming
     @Streaming
     @POST("gists/public")
     Call<ResponseBody> getStreamingBig();
  • @Streaming 表示响应体的数据用流的方式返回,使用于返回数据比较大,该注解在下载大文件时特别有用
7.@Multipart、@part、@PartMap
    @Multipart
	@POST("user/followers")
	Call<ResponseBody> getPartData(@Part("name") RequestBody name, @Part MultipartBody.Part file);
  • @Multipart 表示请求实体是一个支持文件上传的表单,需要配合@Part和@PartMap使用,适用于文件上传

  • @Part 用于表单字段,适用于文件上传的情况,@Part支持三种类型:RequestBody、MultipartBody.Part、 任意类型

  • @PartMap 用于多文件上传, 与@FieldMap和@QueryMap的使用类似

代码使用逻辑:

    //声明类型,这里是文字类型
     MediaType textType = MediaType.parse("text/plain");
    //根据声明的类型创建RequestBody,就是转化为RequestBody对象
    RequestBody name = RequestBody.create(textType, "周润发");

​ 首先声明类型,通过MediaType实现类型的声明,此处使用的时文本类型,然后会根据该类型转化为RequestBody对象,此处使用的参数委RequestBody对象,相当于讲周润发以文本格式转换委RequestBody对象,最终上传时与name形成键值对。

//创建文件,这里演示图片上传
File file = new File("文件路径");
if (!file.exists()) {
   file.mkdir();
 }
 
//将文件转化为RequestBody对象
//需要在表单中进行文件上传时,就需要使用该格式:multipart/form-data
RequestBody imgBody = RequestBody.create(MediaType.parse("image/png"), file);
//将文件转化为MultipartBody.Part
//第一个参数:上传文件的key;第二个参数:文件名;第三个参数:RequestBody对象
MultipartBody.Part filePart = MultipartBody.Part.createFormData("key", file.getName(), imgBody);

对于文件类型的上传,我们一般使用MultipartBody.Part类型上传,在上面讲两个参数设置好后,然后调用接口使用请求方法即可:

Call<ResponseBody> partDataCall = retrofit.create(Api.class).getPartData(name, filePart);

此种格式属于文本与文件混合发送的格式。

@PartMap的使用与@FieldMap和@QueryMap的使用类似,用于多文件上传,我们直接看代码:

    @Multipart
    @POST("user/followers")
    Call<ResponseBody> getPartMapData(@PartMap Map<String, MultipartBody.Part> map);
    File file1 = new File("文件路径");
    File file2 = new File("文件路径");
        if (!file1.exists()) {
        file1.mkdir();
    }
        if (!file2.exists()) {
        file2.mkdir();
    }
 
    RequestBody requestBody1 = RequestBody.create(MediaType.parse("image/png"), file1);
    RequestBody requestBody2 = RequestBody.create(MediaType.parse("image/png"), file2);
    MultipartBody.Part filePart1 = MultipartBody.Part.createFormData("file1", file1.getName(), requestBody1);
    MultipartBody.Part filePart2 = MultipartBody.Part.createFormData("file2", file2.getName(), requestBody2);
 
    Map<String,MultipartBody.Part> mapPart = new HashMap<>();
        mapPart.put("file1",filePart1);
        mapPart.put("file2",filePart2);
8. 有关MediaType方法
  1. 概述:MediaType,即是Internet Media Type,互联网媒体类型,也叫做MIME类型,在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。(也就是说MediaType在网络协议的消息头里面叫做Content-Type)它使用两部分的标识符来确定一个类型,是为了表明我们传的东西是什么类型。

  2. 常见的媒体格式类型如下:

    • text/html : HTML格式
    • text/plain :纯文本格式
    • text/xml : XML格式
    • image/gif :gif图片格式
    • image/jpeg :jpg图片格式
    • image/png:png图片格式

    以application开头的媒体格式类型:

    • application/xhtml+xml :XHTML格式
    • application/xml :XML数据格式
    • application/atom+xml :Atom XML聚合格式
    • application/json :JSON数据格式
    • application/pdf :pdf格式
    • application/msword : Word文档格式
    • application/octet-stream : 二进制流数据(如常见的文件下载、上传)
    • application/x-www-form-urlencoded : 中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式

    另外一种常见的媒体格式是上传文件之时使用的:

    • multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式

四:实例讲解

使用 Retrofit 的步骤共有7个:

  1. 添加Retrofit的依赖库
  2. 创建接收服务器返回数据的类
  3. 定义一个接口(封装URL地址和数据请求)
  4. 实例化Retrofit
  5. 通过Retrofit实例创建接口服务对象
  6. 接口服务对象调用接口中方法,获得Call对象
  7. Call对象执行请求(异步、同步请求)
实例4.1(演示GET请求)

使用Api为前言get请求Api:“https://devapi.qweather.com/v7/weather/now?location=101010100&key=a5cf6ab782a14013b08fb92a57dd2f72”

  1. 添加Retrofit依赖库:(由于Retrofit库已经封装有okhttp所以不需要再添加okhttp的依赖库)

    	implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    
  2. 在清单中添加网络权限

        <uses-permission android:name="android.permission.INTERNET"/>
    
  3. 创建服务器返回数据的接收类:

    Api为一和风天气Api,返回数据类内容自己将url复制至浏览器,你会得到如下数据(一天天气数据):

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Bzp6Tmq-1638885840654)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20211207205030787.png)]

    若将now更改为7d,你将得到如下数据(七天天气数据):

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z0fwQ6eV-1638885840654)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20211207205116238.png)]

    根据返回的json数据类型我们可以得到如下解析类:

    package com.example.retrofit;
    
    import com.google.gson.annotations.SerializedName;
    
    public class Wth_now_out {
        @SerializedName("code")
        private String code;
    
        @SerializedName("now")
        private Wth_now now;
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public Wth_now getNow() {
            return now;
        }
    
        public void setNow(Wth_now now) {
            this.now = now;
        }
    
        class Wth_now {
            @SerializedName("temp")
            private String temp;
    
            @SerializedName("feelsLike")
            private String feelsLike;
    
            @SerializedName("text")
            private String wth_text;
    
            @SerializedName("windDir")
            private String windDir;
    
            @SerializedName("windScale")
            private String windScale;
    
            @SerializedName("humidity")
            private String humidity;
    
            @SerializedName("pressure")
            private String pressure;
    
    
            public String getPressure() {
                return pressure;
            }
    
            public void setPressure(String pressure) {
                this.pressure = pressure;
            }
    
            public String getTemp() {
                return temp;
            }
    
            public void setTemp(String temp) {
                this.temp = temp;
            }
    
            public String getFeelsLike() {
                return feelsLike;
            }
    
            public void setFeelsLike(String feelsLike) {
                this.feelsLike = feelsLike;
            }
    
            public String getWth_text() {
                return wth_text;
            }
    
            public void setWth_text(String wth_text) {
                this.wth_text = wth_text;
            }
    
            public String getWindDir() {
                return windDir;
            }
    
            public void setWindDir(String windDir) {
                this.windDir = windDir;
            }
    
            public String getWindScale() {
                return windScale;
            }
    
            public void setWindScale(String windScale) {
                this.windScale = windScale;
            }
    
            public String getHumidity() {
                return humidity;
            }
    
            public void setHumidity(String humidity) {
                this.humidity = humidity;
            }
        }
    }
    

    另外上url的location的参数也可发生更改,更改后将查询别地的太难器数据。

    此处使用到Gson解析数据的注解,所以需要添加Gson的依赖库:

    implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.7'
    
  4. 创建用于描述网络请求的接口

    public interface ApiService {
        //此时我们将URl分割,并使用path注解使now的值随输入值改变。
        //location与key即?后的部分由QueryMap注解最终通过方法录入。
        @GET("v7/weather/{time}")
        Call<Wth_now_out> getCall(@Path ("time") String time, @QueryMap Map<String, Object> map);
    
    }
    

    在方法头部添加@GET注解,表示采用get方法访问网络请求,内部通过path最终录入{}占位符的值,

  5. 实例化Retrofit

    String baseUrl = "https://devapi.qweather.com/";
    Retrofit mRetrofit = new Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    

    通过new Retrofit.Builder().buile()构建Retrofit实例对象,同时设置baseUrl地址,Retrofit把网络请求的URL 分成了两部分设置:
    第一部分:在创建Retrofit实例时通过.baseUrl()设置,

    	.baseUrl(baseUrl)
    

    第二部分在接口注解里设置:

    	@GET("https://devapi.qweather.com/")
    

    上面两部分拼接后此时URl将为https://devapi.qweather.com/v7/weather/{time},需要注意baseUrl必须以‘/’结尾,否则会报错。

    然后上面还添加了一个Conterter,这是一个解析器,它可将返回的json数据直接转换为我们所定义的json对象接收类。然后解析器也需要添加依赖:

    	implementation 'com.squareup.retrofit2:converter-gson:2.0.2'
    

    Retrofit支持多种数据解析,需要在Gradle添加依赖:

    数据解析器Gradle依赖
    Gsoncom.squareup.retrofit2:converter-gson:2.0.2
    Jacksoncom.squareup.retrofit2:converter-jackson:2.0.2
    Simple XMLcom.squareup.retrofit2:converter-simplexml:2.0.2
    Protobufcom.squareup.retrofit2:converter-protobuf:2.0.2
    Moshicom.squareup.retrofit2:converter-moshi:2.0.2
    Wirecom.squareup.retrofit2:converter-wire:2.0.2
    Scalarscom.squareup.retrofit2:converter-scalars:2.0.2
  6. 通过Retrofit实例创建接口服务对象

    	ApiService apiService = mRetrofit.create(ApiService.class);
    
  7. 接口服务对象调用接口中方法,获得Call对象

        Map<String ,Object> map = new HashMap<>();
        map.put("location","101010100");
        map.put("key","a5cf6ab782a14013b08fb92a57dd2f72");
        Call<Wth_now_out> call = apiService.getCall("now",map);	//获得call对象
    

    此时传入了url的剩余部分,此时方法第一个参数传入now,它将替代原url占位符的位置,通过map向url传入了location和key的值,此时url将成为一个完整的url=“https://devapi.qweather.com/v7/weather/now?location=101010100&key=a5cf6ab782a14013b08fb92a57dd2f72”。

  8. Call对象执行请求(异步、同步请求)

    findViewById(R.id.get).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //发送异步请求
            call.clone().enqueue(new Callback<Wth_now_out>() {
                @Override
                public void onResponse(Call<Wth_now_out> call, Response<Wth_now_out> response) {
                    Wth_now_out body = response.body();
                    Wth_now_out.Wth_now now = body.getNow();
                    String text = "天气:" + now.getWth_text() + ";\n气温:" + now.getTemp()
                            + ";\n气压:" + now.getPressure() + ";\n风向:" + now.getWindDir();
                    tv.setText(text);
                }
    
                @Override
                public void onFailure(Call<Wth_now_out> call, Throwable t) {
    
                }
            });
        }
    });
    

    此处一个需要注意的点,retrofit一个call对象只能发送一次,若同一个call发送两次请求,此时会抛出如下异常:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24hCI8Yx-1638885840655)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20211207213517573.png)]

    对于此种问题的解决,retrofit的call对象提供一个clone()方法,此方法将拷贝一个新的call对象,所以需要重复发送同一请求时,我们就可使用此方法现拷贝一个新的call对象,然后在发送请求。

    效果展示:
    在这里插入图片描述

重复点击get按钮也不会返送异常。

实例4.2(演示POST请求)

参照GET请求,重复的步骤将不做赘述。

此post请求使用APi为:https://v2.alapi.cn/api/dog
在这里插入图片描述
此接口需要向发送如上两个表单(键值对)。

此为ALAPI网站提供学者练习使用的一个API,可以自己去网站寻找接口进行新的实验。

创建网络请求接口:

public interface ApiService {
    
    @FormUrlEncoded		//配合post的Field或FieldMap注解使用。
    @POST//("api/dog")
    //此处我们用一个Object对象接收数据
    Call<Object> postCall(@Url String url,@FieldMap Map<String,Object> map);

}

调用网络请求接口实例,处理返回数据(此接口数据并未解析,直接以Json类型接收并展示):

    
	//创建Retrofit实例
	Retrofit mRetrofit2 = new Retrofit.Builder()
        .baseUrl("https://v2.alapi.cn/")
        .addConverterFactory(GsonConverterFactory.create())
        .build();
	//根据Retrofit实例创建接口服务对象
    ApiService apiService1 = mRetrofit2.create(ApiService.class);
	
	//创建map用于参数输入
    Map<String,Object> map1 = new HashMap<>();
    map1.put("token","fTN2p6btdPMrU2Lf");
    map1.put("format","json");
	//调用请求方法,获得call对象
    Call<Object> post = apiService1.postCall("api/dog",map1);

    findViewById(R.id.post).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //发送异步请求
            post.clone().enqueue(new Callback<Object>() {
                @Override
                public void onResponse(Call<Object> call, Response<Object> response) {
                    Object body = response.body();
                    if(body == null) return;
                    //接收数据,展示数据
                    tv.setText(response.body().toString());
                }

                @Override
                public void onFailure(Call<Object> call, Throwable t) {
                    Log.d("TAG", "onFailure: ");
                }
            });
        }
    });

效果展示:
在这里插入图片描述
结束……