1.Retrofit基本用法
1.1 使用前的准备工作
首先配置build.gradle,如下
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
}
最后一行是为了增加支持返回值为Gson类型数据所需要的依赖包。如果想增加其他类型的数据支持,可以添加其他依赖包。
当然,不要完了在manifest中加入访问网络的权限。
1.2 Retrofit的注解分类
Retrofit与其他请求框架不同的是,它使用了注解。Retrofit的注解分为三大类,分别是HTTP请求方法注解、标记类注解和参数注解。其中,HTTP请求方法注解有8种,它们是GET、POST、PUT、DELETE、HEAD、PATCH、OPTION和HTTP。其前7中分别对应HTTP的请求方法:HTTP可以替换以上7种,也可以扩展请求方法。标记类注解有3种,它们是FormUrlEncoded、Multipart、Streaming。FromUrlEncoded和Multipart后面会讲到;Streaming是代表响应数据以流的形式返回。如果不使用它,会默认把全部数据加载到内存,所以下载大文件时需要加上这个注解。参数注解有Header、Headers、Body、Path、Field、FieldMap、Part、PartMap、Query和QueryMap等,下面会介绍几种参数类注解的用法。
3、GET请求访问网络
首先实现用GET请求方式类访问网络,这里我们访问淘宝IP库。实体类的编写就不再赘述了。首先编写请求网络接口,如下:
public interface IpService {
@GET("getIpInfo.php?ip=59.108.54.37")
Call<IpModel> getIpMsg();
}
Retrofit提供的请求方式注解有@GET和@POST等,分别代表GET请求和POST请求,我们在这里用的是GET请求,访问的地址是"getIpInfo.php?ip=59.108.54.37"。另外定义了getIpMsg方法,这个方法返回Call<IpModel>
类型的参数。接下来创建Retrofit,并创建接口文件,代码如下所示:
String url = "http://ip.taobao.com/service/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url) .addConverterFactory(GsonConverterFactory.create())
.build();
IpServiceForQuery ipService = retrofit.create(IpServiceForQuery.class);
Call<IpModel> call = ipService.getIpMsg();
Retrofit是通过建造者模式构建出来的。请求URL是拼接而成的,它是由baseUrl传入URL加上请求网络接口的@GET(“getIpInfo.php?ip=59.108.54.37”)中的URL拼接而成的。接下来用Retrofit动态代理获取到定义的接口,并调用该接口定义的getIpMsg方法得到Call对象。接下来用Call请求网络并处理回调,代码如下:
call.enqueue(new Callback<IpModel>() {
@Override
public void onResponse(Call<IpModel> call, Response<IpModel> response) {
String country = response.body().getData().getCountry();
Log.i("wangshu", "country" + country);
Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<IpModel> call, Throwable t) {
}
});
这里是异步请求网络,回调的Callback是运行在UI线程中的。得到返回的Response后将返回数据的country字段用Toast显示出来。如果想同步请求网络,可以使用call.execute();如果想中断网络请求,可以使用call.cancle();
动态配置URL地址:@Path
Retrofit提供了很多请求参数注解,这使得请求网络时更加便捷。其中,@Path用来动态配置URL地址。请求网络接口代码如下所示:
public interface IpServiceForPath {
@GET("{path}/getIpInfo.php?ip=59.108.54.37")
Call<IpModel> getIpMsg(@Path("path") String path);
}
在GET注解中包含了{path},它对应着@path中的"path",而用来替换{path}的正是需要传入的"String path"的值。请求网络的代码如下:
String url = "http://ip.taobao.com/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
IpServiceForPath ipService = retrofit.create(IpServiceForPath.class);
Call<IpModel> call = ipService.getIpMsg(path);
call.enqueue(new Callback<IpModel>() {
@Override
public void onResponse(Call<IpModel> call, Response<IpModel> response) {
String country = response.body().getData().getCountry();
Log.i("wangshu", "country" + country);
Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<IpModel> call, Throwable t) {
}
});
动态指定查询条件:@Query
之前的例子就是为了查询ip的地址,每次查询更换不同的ip就可以了,可以用@Qurey来动态地指定ip的值。请求网络接口的代码如下:
public interface IpServiceForQuery{
@GET("getIpInfo.php")
Call<IpModel> getIpMsg(@Query("ip")String ip);
}
请求网络的时候,只需要传入想要查询的ip值就可以了。
动态指定查询条件组:@QureyMap
在网络请求中一般为了更精确的查找到我们所需要的数据,需要传入很多查询参数。如果用@Query会比较麻烦,这时我们可以采用@QueryMap,将所有的参数集成在一个Map中统一传递,如下所示:
public interface IpServiceForQueryMap {
@GET("getIpInfo.php")
Call<IpModel> getIpMsg(@QueryMap Map<String, String> options);
}
如果需要详细的 日志信息,
网络请求日志
调试网络请求的时候经常需要关注一下请求参数和返回值,以便判断和定位问题出在哪里,Retrofit官方提供了一个很方便查看日志的Interceptor,你可以控制你需要的打印信息类型,使用方法也很简单。
首先需要在build.gradle文件中引入logging-interceptor
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
添加到OkHttpClient创建处即可,完整的示例代码如下:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(loggingInterceptor);
String url = "http://ip.taobao.com/service/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.client(builder.build())
.build();
HttpLoggingInterceptor提供了4中控制打印信息类型的等级,分别是NONE,BASIC,HEADERS,BODY,接下来分别来说一下相应的打印信息类型。
- NONE
没有任何日志信息 - Basic
打印请求类型,URL,请求体大小,返回值状态以及返回值的大小
D/HttpLoggingInterceptor L o g g e r : − − > P O S T / u p l o a d H T T P / 1.1 ( 277 − b y t e b o d y ) D / H t t p L o g g i n g I n t e r c e p t o r Logger: --> POST /upload HTTP/1.1 (277-byte body) D/HttpLoggingInterceptor Logger:−−>POST/uploadHTTP/1.1(277−bytebody)D/HttpLoggingInterceptorLogger: <-- HTTP/1.1 200 OK (543ms, -1-byte body)
- Headers
打印返回请求和返回值的头部信息,请求类型,URL以及返回值状态码
<-- 200 OK https://api.douban.com/v2/book/search?q=小王子&start=0&count=3&token=tokenValue (3787ms)
D/OkHttp: Date: Sat, 06 Aug 2016 14:26:03 GMT
D/OkHttp: Content-Type: application/json; charset=utf-8
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Connection: keep-alive
D/OkHttp: Keep-Alive: timeout=30
D/OkHttp: Vary: Accept-Encoding
D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
D/OkHttp: Pragma: no-cache
D/OkHttp: Cache-Control: must-revalidate, no-cache, private
D/OkHttp: Set-Cookie: bid=D6UtQR5N9I4; Expires=Sun, 06-Aug-17 14:26:03 GMT; Domain=.douban.com; Path=/
D/OkHttp: X-DOUBAN-NEWBID: D6UtQR5N9I4
D/OkHttp: X-DAE-Node: dis17
D/OkHttp: X-DAE-App: book
D/OkHttp: Server: dae
D/OkHttp: <-- END HTTP
- Body
打印请求和返回值的头部和body信息
– 200 OK https://api.douban.com/v2/book/search?q=小王子&tag=&start=0&count=3&token=tokenValue (3583ms)
D/OkHttp: Connection: keep-alive
D/OkHttp: Date: Sat, 06 Aug 2016 14:29:11 GMT
D/OkHttp: Keep-Alive: timeout=30
D/OkHttp: Content-Type: application/json; charset=utf-8
D/OkHttp: Vary: Accept-Encoding
D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Pragma: no-cache
D/OkHttp: Connection: keep-alive
D/OkHttp: Cache-Control: must-revalidate, no-cache, private
D/OkHttp: Keep-Alive: timeout=30
D/OkHttp: Set-Cookie: bid=ESnahto1_Os; Expires=Sun, 06-Aug-17 14:29:11 GMT; Domain=.douban.com; Path=/
D/OkHttp: Vary: Accept-Encoding
D/OkHttp: X-DOUBAN-NEWBID: ESnahto1_Os
D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
D/OkHttp: X-DAE-Node: dis5
D/OkHttp: Pragma: no-cache
D/OkHttp: X-DAE-App: book
D/OkHttp: Cache-Control: must-revalidate, no-cache, private
D/OkHttp: Server: dae
D/OkHttp: Set-Cookie: bid=5qefVyUZ3KU; Expires=Sun, 06-Aug-17 14:29:11 GMT; Domain=.douban.com; Path=/
D/OkHttp: X-DOUBAN-NEWBID: 5qefVyUZ3KU
D/OkHttp: X-DAE-Node: dis17
D/OkHttp: X-DAE-App: book
D/OkHttp: Server: dae
D/OkHttp: {“count”:3,“start”:0,“total”:778,“books”:[{“rating”:{“max”:10,“numRaters”:202900,“average”:“9.0”,“min”:0},“subtitle”:"",“author”:["[法] 圣埃克苏佩里"],“pubdate”:“2003-8”,“tags”:[{“count”:49322,“name”:“小王子”,“title”:“小王子”},{“count”:41381,“name”:“童话”,“title”:“童话”},{“count”:19773,“name”:“圣埃克苏佩里”,“title”:“圣埃克苏佩里”}
D/OkHttp: <-- END HTTP (13758-byte body)
4.POST请求访问网络
传输数据类型为键值对:@Field
传输数据类型为键值对,这是我们最常用的POST请求数据类型,淘宝IP库支持数据类型为兼职对的POST请求。请求网络接口的代码如下所示:
public interface IpServiceForPost {
@FormUrlEncoded
@POST("getIpInfo.php")
Call<IpModel> getIpMsg(@Field("ip") String first);
}
首先用@FormUrlEncoded注解来标明这是一个表单请求,然后再getIpMsg方法中使用@Field注解来标示所对应的String类型数据的键,从而组成一组键值对进行传递。请求网络代码如下:
String url = "http://ip.taobao.com/service/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
IpServiceForPost ipService = retrofit.create(IpServiceForPost.class);
Call<IpModel> call = ipService.getIpMsg(ip);
call.enqueue(new Callback<IpModel>() {
@Override
public void onResponse(Call<IpModel> call, Response<IpModel> response) {
String country = response.body().getData().getCountry();
Log.i("wangshu", "country" + country);
Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<IpModel> call, Throwable t) {
}
});
传输数据类型JSON字符串:@Body
我们也可以用POST方式将字符串作为请求体发送到服务器,请求网络接口的代码如下:
public interface IpServiceForPostBody {
@POST("getIpInfo.php")
Call<IpModel> getIpMsg(@Body Ip ip);
}
用@Body这个注解标识参数即可,Retrofit会将Ip对象转换为字符串:
public class Ip {
private String ip;
public Ip(String ip) {
this.ip = ip;
}
}
请求网络的代码基本一致:
...
IpServiceForPostBody ipService = retrofit.create(IpServiceForPostBody.class);
Call<IpModel> call = ipService.getIpMsg(new Ip(ip));
...
抓包可以看到,请求数据实际上是一个json字符串
单个文件上传:@Part
public interface UploadFileForPart {
@Multipart
@POST("user/photo")
Call<User> updateUser(@Part MultipartBody.Part photo, @Part("description")RequestBody description);
}
Multipart注解允许多个@Part。updateUser方法的第一个参数是准备上传的图片文件,使用了MultipartBody类型;另一个参数是RequestBody类型,它用来传递简单的键值对。请求网络代码如下:
...
File file = new File(Environment.getExternalStorageDirectory(), "devyu.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("photos", "devyu.png", photoRequestBody);
UploadFileForPart uploadFilefile = retrofit.create(UploadFileForPart.class);
Call<User> call=uploadFilefile.updateUser(photo,RequestBody.create(null,"devyu"));
...
多个文件上传@PartMap
@Multipart
@POST("user/photo")
Call<IpModel> updateUser(@PartMap Map<String,RequestBody> photos, @Part("description")RequestBody description);
这和单个文件是类似的,只不过用了Map封装了上传的文件,并用@PartMap注解来标识。其他的都和单文件上传一样
5.消息报头
在HTTP请求中,为了防止攻击或者过滤掉不安全的访问,或者添加特殊加密的访问等,以便减轻服务器的压力和请求的安全,通常会在消息报头携带一些特殊的消息头。Retrofit也提供了@Header来添加消息报头。添加消息报头有两种方式:一种是静态的,一种是动态的。
静态的如下所示:
interface SomeService{
@GET("some/endpoint")
@Headers("Accept-Encoding:application/json")
Call<ResponseBody> getCarType();
}
使用@Headers注解添加消息报头。如果需要添加多个消息报头,则可以使用{}括起来:
interface SomeService{
@GET("some/endpoint")
@Headers({"Accept-Encoding:application/json",
"User-Agent:MoonRetrofit"
})
Call<ResponseBody> getCarType();
}
以动态方式添加消息报头方式如下:
interface SomeService{
@GET("some/endpoint")
Call<ResponseBody> getCarType(@Header("location") String location);
}
使用@Header注解,可以通过getCarType方法动态添加消息报头。