Retrofit本身并没有提供网络访问的能力,但是它底层封装了OkHttp,也是由Square公司贡献的一个处理网络请求的开源项目
A type-safe HTTP client for Android and Java
https://github.com/square/retrofit
Retrofit的基本使用
Retrofit中的注解
文件上传与下载
Retrofit 嵌套请求与适配器
Retrofit的转换器
一、Retrofit的基本使用
在项目模块的build.gradle中添加依赖:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
(示例)服务器接口信息:
服务器域名:https://www.httpbin.org/
接口:post
参数:username, password
接口:get
参数:username, password
(1)根据HTTP接口创建 Java 接口
import okhttp3.ResponseBody; //注意这里是okhttp3的包
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.http.POST
import retrofit2.http.GET
public interface HttpbinService{
@GET("get")
Call<ResponseBody> get(@Query("username") String username, @Query("password") String pwd);
@POST("post")
@FormUrlEncoded
Call<ResponseBody> postForm(@Field("username") String username, @Field("password") String pwd);
}
(2)创建Retrofit对象,并生成接口实现类对象
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org/").build();
HttpbinService httpbindService = retrofit.create(HttpbinService.class);
(3)接口实现类对象调用对应方法获得响应
retrofit2.Call<ResponseBody> call = httpbindService.post("lance", "123");
call.enqueue(new retrofit2.Call<ResponseBody> call, retrofit2.Response<ResponseBody> response){
@Override
public void onResponse(retrofit2.Call<ResponseBody> call, retrofit2.Response<ResponseBody> response){
Log.i(TAG, "postAsync::" + response.body().string());
}
@Override
public void onFailure(retrofit2.Call<ResponseBody> call, Throwable t){
}
});
相对于OkHttp来说,Retrofit的使用较为简洁
二、Retrofit中的注解
- 方法注解:@GET, @POST, @PUT, @DELETE, @PATH, @HEAD, @OPTIONS, @HTTP
- 标记注解:@FormUrlEncoded, @Multipart, @Streaming
- 参数注解:@Query, @QueryMap, @Body, @Field, @FieldMap, @Part, @PartMap
- 其他注解:@Path, @Header, @Headers, @Url
1、方法注解
(1)@GET
get网络请求
代码示例:
一个简单的get请求
baseURL: http://102.10.10.132/api
请求网址: http://102.10.10.132/api/News
@GET("News")
Call<NewsBean> getItem();
(2)@POST
(3)@PUT
(4)@DELETE
(5)@PATH
(6)@HEAD
(7)@OPTIONS
(8)@HTTP
2、标记注解
(1)@FormUrlEncoded
用于修饰 @Field注解 和 @FieldMap 注解,将会自动将请求参数的类型调整为 application/x-www-form-urlencoded
(2)@Multipart
(3)@Streaming
3、参数注解
(1)@Query
主要用于 Get 请求数据,用于拼接 Url 路径后面的查询参数,一个 @Query 相当于拼接一个参数,多个参数中间用 “,” 隔开。
参数以 “?key1=value1&key2=value2” 的形式拼接在Url的后面。
在Post请求中,尽量不要使用 @Query 和 @QueryMap,因为它传入的参数是直接拼接在 url 上的,不安全,而 @Field 和 @FieldMap 是写入到 Body 中的。
示例:
baseUrl为:http://mock-api.com/2vKVbXK8.mock/
访问地址:http://mock-api.com/2vKVbXK8.mock/api/getUserInfo?id=1234
@GET("api/getUserInfo")
Call<UserInfo> getUserInfo(@Query("id") String userId);
(2)@QueryMap
效果等同于多个@Query参数拼接,主要也用于Get请求网络数据。
示例:
baseUrl为:http://mock-api.com/2vKVbXK8.mock/
访问地址:http://mock-api.com/2vKVbXK8.mock/api/getArticalInfo?id=405&page=1
@GET("api/getArticalInfo")
Call<ArticalInfo> getArticalInfo(@QueryMap Map<String, String> params);
Map<String, String> params = new HashMap<>();
params.put("id", "405");
params.put("page", "1");
getApi.getArticalInfo(params).enqueue(new Callback<ArticalInfo>() {
@Override
public void onResponse(Call<ArticalInfo> call, Response<ArticalInfo> response) {
if (response != null && response.body() != null) {
Log.i(TAG, "onRespons:" + response.body().toString());
}
}
@Override
public void onFailure(Call<ArticalInfo> call, Throwable t) {
Log.i(TAG, "onFailure: " + t);
}
});
(3)@Body
请求参数中含多个不同类型
如果使用多个 @Field 较为繁琐
使用 @Body 注解,可直接传入一个对象,对象中可包含多种类型的数据
访问地址:http://mock-api.com/2vKVbXK8.mock/api/bodyParam
代码示例:
@POST("api/bodyParam")
Call<ResponseBody> postBodyFun(@Body PostBodyBean postBodyBean);
public class PostBodyBean {
private String key;
private int num;
private boolean isTrue;
}
PostBodyBean postBodyBean = new PostBodyBean("myfittinglife",1,true);
postApi.postBodyFun(postBodyBean).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
String str = new String(response.body().bytes());
Log.i(TAG, "onResponse: " + str);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i(TAG, "onFailure: " + t);
}
});
其中,不一定要自定义PostBodyBean类,可以使用系统的FormBody
FormBody formBody = new FormBody.Builder()
.add("a", "1")
.add("b", "2")
.build();
注意:
使用@Body注解一定在创建Retrofit的时候加上.addConverterFactory(GsonConverterFactory.create())
,目的是将对象转化为json字符串进行传递,否则会报以下错误
Unable to create @Body converter for class PostBodyBean
(4)@Field
用于 Post 请求,对应于 GET 请求中的 @Query
访问地址:http://mock-api.com/2vKVbXK8.mock/api/fieldParam
@FormUrlEncoded
@POST("api/fieldParam")
Call<ResponseBody> postFieldFun(@Field("key") String key);
private PostApi postApi;
postApi = retrofit.create(PostApi.class);
postApi.postFieldFun("myfittinglife").enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
String str = new String(response.body().bytes());
Log.i(TAG, "onResponse: " + str);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i(TAG, "onFailure: " + t);
}
});
key = myfittinglife
(5)@FieldMap
用于Post请求,对应于 GET 请求中的 @QueryMap
适合多个相同类型参数的传递
访问地址:http://mock-api.com/2vKVbXK8.mock/api/fieldMapParam
@FormUrlEncoded
@POST("api/fieldMapParam")
Call<ResponseBody> postFildMapFun(@FieldMap Map<String, String> params);
Map<String, String> params = new HashMap<>();
params.put("key", "myfittinglife");
params.put("password", "123456");
postApi.postFildMapFun(params).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
String str = new String(response.body().bytes());
Log.i(TAG, "onResponse: " + str);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i(TAG, "onFailure: " + t);
}
});
(6)@Part
(7)@PartMap
“@Part” 和 “@PartMap” 是结合标记注解 “@Multipart“ 一起使用的,和文件上传相关
4、其他注解
(1)@Path
主要用于Get请求,用于替换Url路径中的变量字符
代码示例:
baseUrl为:http://mock-api.com/2vKVbXK8.mock/
访问地址:
http://mock-api.com/2vKVbXK8.mock/api/getDynamicInfo/1/data
http://mock-api.com/2vKVbXK8.mock/api/getDynamicInfo/2/data
@GET("api/getDynamicInfo/{param}/data")
Call<ResponseBody> getDynamicInfo(@Path("param")int paramValue);
该方法通过 GET 请求去访问服务器的 http://mock-api.com/2vKVbXK8.mock/api/getDynamicInfo/{param}/data,其中会通过 @Path 注解,将路径中的 {param} 替换成参数 param 的具体值。
(2)@Header
动态添加单个头部信息
示例:
baseUrl为:http://mock-api.com/2vKVbXK8.mock/
访问地址:http://mock-api.com/2vKVbXK8.mock/api/dynamicHeadersInfo
@GET("api/dynamicHeadersInfo")
Call<ResponseBody> getDynamicHeaderInfo(@Header("version") String version);
getApi.getDynamicHeaderInfo("1.1.1").enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
(3)@Headers
静态添加头部信息:包含添加单个头部、添加多个头部。注解内部以 key:value 的方式填写内容
示例:
baseUrl为:http://mock-api.com/2vKVbXK8.mock/
访问地址:http://mock-api.com/2vKVbXK8.mock/api/staticHeaderInfo
1)静态添加单个头部
@Headers("version:1.1")
@GET("api/staticHeaderInfo")
Call<GetBean> getStaticHeadersInfo();
2)静态添加多个头部
@Headers({"version:1.1",
"type:android"})
@GET("api/staticHeadersInfo")
Call<GetBean> getStaticMoreHeadersInfo();
(4)HeaderMap
动态添加多个头部信息
示例:
baseUrl为:http://mock-api.com/2vKVbXK8.mock/
访问地址:http://mock-api.com/2vKVbXK8.mock/api/dynamicHeadersInfo
@GET("api/dynamicHeadersInfo")
Call<ResponseBody> getDynamicHeadersInfo(@HeaderMap Map<String, String> headers);
Map<String, String> headers = new HashMap<>();
headers.put("version", "2.2.2");
headers.put("type", "Android");
getApi.getDynamicHeadersInfo(headers).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
}
(5)@Url
当要访问的地址不只是动态的变几个参数,而是整个地址都要变化,甚至是基类地址也要变化时,这种动态地址就要用到@Url注解
代码示例:
@GET
Call<ResponseBody> getDynamicUrl(@Url String url);
String url = "http://mock-api.com/2vKVbXK8.mock/api/getDynamicUrlData"
getApi.getDynamicUrl(url).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
String str = new String(response.body().bytes());
Log.i(TAG, "onResponse: " + str);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.i(TAG, "onFailure: " + t);
}
});
所以说这个注解很方便,链接可通过此注解传入完整的Url进行访问。
注意:
虽然说最终访问的地址与原先的baseUrl无关,但是baseUrl还是要以http://
或https://
开头,并且后面至少要跟一个字母或者其他东西,不然就会报错。
1)报错写法
http://
写成上面的内容会报错,Invalid URL host: ""错误。
2)无报错写法
http://a
不会报错,这个a这里写什么都可以,没有以/
结尾也可以
三、文件上传与下载
1、上传文件:
public interface UploadService{
@POST("post")
@Multipart
Call<ResponseBody> upload(@Part MultipartBody.Part file);
} //多个文件可用PartMap
public class UploadFileUnitTest{
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.httpbin.org/").build();
UploadService uploadService = retrofit.create(UploadService.class);
public void uploadFileTest() throw IOException{
File file1 = new File("C:\\Users\\Administrator\\Desktop\\1.txt");
MultipartBody.Part part = MultipartBody.Part.createFormData("file1", "1.txt", RequestBody.create(file1, MediaType.parse("text/plain")));
Call<ResponseBody> call = uploadService.upload(part);
System.out.println(call.execute().body().string());
}
}
2、下载文件:
找一个下载链接
添加下载接口
public interface UploadService{
@POST("post")
@Multipart
Call<ResponseBody> upload(@Part MultipartBody.Part file);
@GET
Call<ResponseBody> download(@Url string url);
@GET
Flowable<ResponseBody> downloadRxJava(@Url string url);
}
先学会基本使用,然后了解框架原理,接着学习完成自定义适配器等等
public class UploadFileUnitTest{
...
public void downloadTest(){
Response<ResponseBody> response = uploadService.download("下载链接(自己随便找一个)").execute();
//if response.isSuccessful()
InputStream inputStream = response.body().byteStream(); //试试string()呢?
FileOutputStream fos = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\a.apk");
int len;
byte[] buff = new byte[4096];
while((len = inputStream.read(buffer)) != -1){
//继续读
fos.write(buffer, 0, len);
}
fos.close();
inputStream.close();
}
public void downloadRxjavaTest(){
uploadService.downloadRxJava("下载链接(自己随便找一个)")
.map(new Function<ResponseBody, File>()){
@Override
public File apply(ResponseBody responseBody) throws Throwable{
InputStream inputStream = responseBody.byteStream();
//if response.isSuccessful()
InputStream inputStream = response.body().byteStream(); //试试string()呢?
File file = new File("C:\\Users\\Administrator\\Desktop\\a.apk");
FileOutputStream fos = new FileOutputStream(file);
int len;
byte[] buff = new byte[4096];
while((len = inputStream.read(buffer)) != -1){
//继续读
fos.write(buffer, 0, len);
}
fos.close();
inputStream.close();
return file;
}
}).subscribe(new Consumer<File>(){
@Override
public void accept(File file) throws Throwable{
}
});
while(true){
}
}
}
还有需要注意的一点,如果下载的文件特别大,则在写Java接口时,需加上 “@Streaming" 注解,:
public interface UploadService{
@POST("post")
@Multipart
Call<ResponseBody> upload(@Part MultipartBody.Part file);
@Streaming
@GET
Call<ResponseBody> download(@Url string url);
@Streaming
@GET
Flowable<ResponseBody> downloadRxJava(@Url string url);
}
在下载文件中,“@Streaming” 注解非常关键,可以有效避免内存溢出的问题,该注解表示以流的方式获取文件数据。
四、Retrofit 嵌套请求与适配器
1、Retrofit的嵌套请求
在实际开发中,可能会存在:需要先请求A接口,再请求B接口的情况。比如需要请求获取收藏文章列表,但是需要先登录拿到Cookie才能请求收藏文章列表接口。此时请求就有了先后顺序,为了完成这个功能,我们需要这样实现代码:
//先登录
Call<BaseResponse> call = wanAndroidService2.login("lanceedu", "123123");
call.enqueue(new Callback<BaseResponse>(){
@Override
public void onResponse(Call<BaseResponse> call, Response<BaseResponse) response){
//登录成功请求收藏的文章
if(response.isSuccessful()){
wanAndroidService2.getArticle(0).enqueue(new Callback<ResponseBody>(){
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response){
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t){
}
});
}
}
@Override
public void onFailure(Call<BaseResponse> call, Throwable t){
}
});
2、Retrofit的适配器
Retrofit的接口方法返回类型必须是Call,如果能够将Call改为RxJava中的Observable,对于嵌套的情况,就能得到非常方便优雅的解决。这就是适配器的功能,如果我们想要返回的不是Call,适配器就能帮助我们转换为其它类型。
以RxJava3为例:
添加依赖:
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
修改接口方法:
@POST("post")
@FormUrlEncoded
Observable<JavaBean> post(@Field("username") String userName, @Field("password")String pwd);
在项目中:
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
//android项目一般也会引入rxandroid
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
@POST("user/login")
@FormUrlEncoded
Flowable<BaseResponse> login2(@Field("username") String userName, @Field("password")String pwd);
@GET("lg/collect/list/{pageNum}/json")
Flowable<ResponseBody> getArticle(@Path("pageNum") int pageNum);
使用适配器
Retrofit retrofit3 = new Retrofit.Builder()
.baseUrl("https://www.wanandroid.com/")
.callFactory(new OkHttpClient.Builder().cookieJar(new CookieJar(){
@Override
public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list){
cookies.put(httpUrl.host(), list);
}
@Override
public List<Cookie> loadForRequest(HttpUrl httpUrl){
List<Cookie> cookies = WanAndroidUnitTest.this.cookies.get(httpUrl.host());
return cookies == null ? new ArrayList<>() : cookies;
}
}).build()) //添加Cookie
.addConverterFactory(GsonConverterFactory.create()) //添加转换器
.addCallAdapterFactory(RxJava3CallAdapterFactory.create()) //添加适配器
.build();
WanAndroidService2 wanAndroidService3 = retrofit3.create(WanAndroidService2.class);
public void rxjavaTest(){
wanAndroidService3.login2("lanceedu","123123")
.flatMap(new Function<BaseResponse, Publisher<ResponseBody>>(){
@Override
public Publisher<ResponseBody> apply(BaseResponse baseResponse) throw Throwable{
return wanAndroidService3.getArticle(0);
}
}) //根据login2的结果,再去生成一个新的publish
.observeOn(Scheduler.io()) //切换线程
//.subscribeOn(Schedulers.newThread()) //Java环境切换主线程
.subscribeOn(AndroidSchedulers.mainThread()) //观察的回调切换到Android主线程
.subscribe(new Consumer<BaseResponse>(){
@Override
public void accept(ResponseBody responseBody) throw Throwable{
System.out.println(responseBody.string());
}
});
while(true){
}
}
RxJava的转换器和适配器有很多(可以去github上查看),也可以自定义
五、Retrofit的转换器
在我们接到服务器的响应后,目前无论是OkHttp还是Retrofit都只能接收到String字符串类型的数据,在实际开发中,我们经常需要对字符串进行解析将其转变为一个Java Bean对象。比如服务器响应数据为JSON格式字符串,那么我们可以自己利用GSON库完成反序列化的操作。
而Retrofit提供了多个转换器使得响应能够完成自动的数据转换。以json解析为例:
添加依赖:
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
修改接口方法:
@POST("post")
@FormUrlEncoded
Call<JavaBean> post(@Field("username")String userName, @Field("password")String pwd);
使用wanandroid的API(wanandroid.com)进行实验
调用登录接口
1、常规写法:
针对登录接口,创建Java接口:
public class WanAndroidService{
@POST("user/login")
@FormUrlEncoding
Call<ResponseBody> login(@Field(username)String username, @Field(password)String pwd);
}
public class WanAndroidUnitTest{
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.wanandroid.com").build();
WanAndroidService wanAndroidService = retrofit.create(WanAndroidService.class);
@Test
public void loginTest(){
Call<ResponseBody> call = wanAndroidService.login("lanceedu", "123123");
Response<ResponseBody> response = call.execute();
String result = response.body().string();
System.our.println(result);
}
}
得到response后,希望将接收数据result转变为一个Java Bean,变为一个对象,那么必须自己去做 json 转换的事情。
json的使用,依赖gson库
implementation 'com.google.code.gson:gson:2.8.6'
然后,创建一个对应的 Java 类型
自己手动写较为繁琐,可以自动
搜索BEJSON,JSON工具 --> JSON生成Java实体类
将Json数据粘贴进去
点击下载文件,将代码目录复制到项目中
(试一下,先用类似postman的工具生成请求数据,然后转一下文件)
public class WanAndroidUnitTest{
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.wanandroid.com").build();
WanAndroidService wanAndroidService = retrofit.create(WanAndroidService.class);
@Test
public void loginTest(){
Call<ResponseBody> call = wanAndroidService.login("lanceedu", "123123");
Response<ResponseBody> response = call.execute();
String result = response.body().string();
System.our.println(result);
//自己完成反序列化
BaseResponse baseResponse = new Gson().fromJson(result, BaseResponse.class);
System.out.println(baseResponse);
}
}
2、使用Retrofit转换器
Retrofit转换器 :自动完成反序列化
//implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:convert-gson:2.9.0'
public class WanAndroidService{
@POST("user/login")
@FormUrlEncoding
//Call<ResponseBody> login(@Field(username)String username, @Field(password)String pwd);
//ResponseBody返回的是String,所以要改为我们自己的BaseResponse
Call<BaseResponse> login(@Field(username)String username, @Field(password)String pwd);
}
public class WanAndroidUnitTest{
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.wanandroid.com").build();
WanAndroidService wanAndroidService = retrofit.create(WanAndroidService.class);
@Test
public void loginTest(){
Call<ResponseBody> call = wanAndroidService.login("lanceedu", "123123");
Response<ResponseBody> response = call.execute();
String result = response.body().string();
System.our.println(result);
}
//添加转换器
Retrofit retrofit2 = new Retrofit.Builder().baseUrl("https://www.wanandroid.com").addConvertFactory(GsonConvert).build();
WanAndroidService wanAndroidService2 = retrofit2.create(WanAndroidService.class);
@Test
public void loginConvertTest(){
Call<ResponseBody> call = wanAndroidService2.login("lanceedu", "123123");
Response<BaseResponse> response = call.execute();
// String result = response.body().string();
// System.our.println(result);
BaseResponse baseResponse = response.body();
System.our.println(baseResponse);
}
}
不加转换器的话,程序不知道该怎么将字符串转换为BaseResponse
其实,转换器内部就是自动帮我们完成的new Gson().fromJson(…) 操作
参考:
https://juejin.cn/post/6844903876559110151#heading-8