什么是Retrofit
Retrofit是一个基于OkHttp的RESTful HTTP网络请求库,是目前比较流行的HttpClient库之一。它使得开发者可以更方便地与Web服务交互,通过简洁的API接口,实现了将REST API请求转化为Java接口调用的方式。
Retrofit的主要特点如下:
- 简单易用:Retrofit使用简洁的注解方式定义REST API的接口,支持多种HTTP请求方法(如GET、POST、PUT、DELETE等),并自动将响应结果转换为Java对象。
- 支持异步请求:Retrofit支持异步网络请求,可以使用RxJava、Kotlin协程等方式实现异步请求,避免在主线程中执行耗时操作。
- 支持自定义拦截器和转换器:Retrofit可以通过自定义拦截器和转换器实现各种功能,如添加请求头、日志记录、错误处理、JSON解析等。
- 支持文件上传和下载:Retrofit支持文件上传和下载,可以将文件作为请求体或响应体发送或接收。
- 支持URL替换和查询参数:Retrofit支持URL替换和查询参数,可以在URL中动态替换参数,或在查询参数中传递参数。
- 兼容多种序列化库:Retrofit支持多种序列化库,如Gson、Jackson等,可以自动将响应结果转换为Java对象。
Retrofit的使用方式
1、定义一个接口(封装URL地址和数据请求)
2、实例化Retrofit
3、通过Retrofit实例创建接口服务对象
4、接口服务对象调用接口中方法,获得Call对象
5、Call对象执行请求(异步、同步请求)
入门案例
通过GET请求访问https://www.wanandroid.com/banner/json
public interface ApiService {
/**
* get无参请求
* https://www.wanandroid.com/banner/json
*/
@GET("banner/json")
Call<ResponseBody> getBanner();
}
创建Retrofit实例时需要通过Retrofit.Builder,并调用baseUrl方法设置URL
public class MainActivity {
public static final String BASE_URL = "https://www.wanandroid.com/";
@Override
protected void onCreate(Bundle savedInstanceState) {
//2.实例化Retrofit对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.build();
//3.通过Retrofit实例创建接口服务对象
ApiService apiService = retrofit.create(ApiService.class);
//4.接口服务对象调用接口中方法,获得Call对象
Call<ResponseBody> call = apiService.getBanner();
//5.Call对象执行请求(异步、同步请求)
//同步请求:不常用,一般使用异步请求
//Response<ResponseBody> execute = call.execute();
//异步请求
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//onResponse方法是运行在主线程也就是UI线程的,所以我们可以在这里直接更新ui
if (response.isSuccessful()) {
try {
String result = response.body().string();
log.info("onResponse: {}", result);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
//onFailure方法是运行在主线程也就是UI线程的,所以我们可以在这里直接更新ui
Toast.makeText(MainActivity.this, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
//call.cancel(); //取消
}
Retrofit注解
请求方式注解
GET有参请求
public interface ApiService {
/**
* get有参请求
* http://qt.qq.com/php_cgi/news/php/varcache?id=12&page=0&plat=android&version=9724
*/
@GET("/news/php/varcache")
Call<ResponseBody> getNewsInfo(@Query("id") String id,
@Query("page") String page,
@Query("plat") String plat,
@Query("version") String version);
}
public class MainActivity extends AppCompatActivity {
public static final String BASE_URL = "http://qt.qq.com/php_cgi";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<ResponseBody> call = apiService.getNewsInfo("12", "0", "android", "9724");
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
try {
String string = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e("xyh", "onFailure: " + t.getMessage());
}
});
}
@Query 注解:用于Get中指定参数。
@QueryMap注解:如果参数过多的话可以使用@QueryMap封装参数,相当于多个@Query
@GET("news/php/varcache")
Call<ResponseBody> getNewsInfo(@QueryMap Map<String, String> map);
Map<String, String> map = new HashMap<>();
map.put("id", "12");
map.put("page", "0");
map.put("plat", "android");
map.put("version", "9724");
Call<ResponseBody> call = apiService.getNewsInfo(map);
POST请求
@FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
/**
* post请求
* FormUrlEncoded:表示请求实体是一个Form表单,每个键值对需要使用@Field注解
* http://qt.qq.com/php_cgi/news/php/update?id=12&page=0&plat=android&version=9724
*/
@FormUrlEncoded
@POST("news/php/update")
Call<ResponseBody> updateGameInfo(@Field("id") String id,
@Field("page") String page,
@Field("plat") String plat,
@Field("version") String version);
多个参数时可以使用,类型@QueryMap
@FormUrlEncoded
@POST("news/php/update")
Call<ResponseBody> updateGameInfo(@FieldMap Map<String, String> map);
POST添加Body
@Body注解:设置请求的请求体
方法一:使用RequestBody
@POST("news/php/send")
Call<ResponseBody> sendNewsInfoByBody(@Body RequestBody Body);
String json = "";
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
retrofit.create(ApiService.class)
.sendNewsInfoByBody(body)
.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
方法二:直接传入实体,它会自行转化成Json,这个转化方式是GsonConverterFactory定义的。
/**
* 直接传入实体,它会自行转化成Json,这个转化方式是GsonConverterFactory定义的。
*/
@POST("news/php/send")
Call<ResponseBody> sendNewsInfoByBody(@Body ParmasBean bean);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create()) //gson转换器
.build();
ApiService apiService = retrofit.create(ApiService.class);
ParmasBean parmasBean= new ParmasBean();
Call<ResponseBody> call = apiService.sendNewsInfoByBody(parmasBean);
方式3:使用Map集合
@POST("news/php/send")
Call<ResponseBody> sendNewsInfoByBody(@Body Map<String, Object> map);
Map<String, Object> parmas = new HashMap<>();
parmas.put("alipay_account", "xx");
parmas.put("real_name", "xx");
Call<ResponseBody> call = apiService.getNewsInfoByBody(parmas);
HTTP注解
这些注解分别对应Http的8种请求方法,注解中都是接收一个字符串作为请求路径的一部分,有点像@RequestMapping(),HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:method、path、hasBody:
请求头注解
注解 | 说明 |
---|---|
@Headers | 用于添加固定请求头,可以同时添加多个,通过该注解添加的请求头不会相互覆盖,而是会共同存在 |
@Header | 作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头 |
@Url注解:用于指定请求的路径,若需要重新定义接口地址,可以使用@Url,将地址以参数的形式传入即可。
/**
* 若需要重新定义接口地址,可以使用@Url,将地址以参数的形式传入即可。
*/
@GET
Call<List<Activity>> getActivityList(@Url String url, @QueryMap Map<String, String> map);
Headers注解
/**
* 使用@Headers添加多个请求头
* 用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在
*/
@Headers({
"User-Agent:android",
"apikey:123456789",
"Content-Type:application/json",
})
@POST()
Call<BaseEntity<NewsInfo>> post(@Url String url, @QueryMap Map<String, String> map);
Header注解
/**
* @Header注解:
*/
@GET("mobile/active")
Call<BaseEntity<NewsInfo>> get(@Header("token") String token, @Query("id") int activeId);
请求参数注解
请求参数注解已经在上面的案例中进行了介绍,这里进行简单的总结:
请求参数注解 | 说明 |
---|---|
@Body | 多用于Post请求发送非表达数据,根据转换方式将实例对象转化为对应字符串传递参数,比如使用Post发送Json数据,添加GsonConverterFactory则是将body转化为json字符串进行传递 |
@Filed | 多用于Post方式传递参数,需要结合@FromUrlEncoded使用,即以表单的形式传递参数 |
@FiledMap | 多用于Post请求中的表单字段,需要结合@FromUrlEncoded使用 |
@Part | 用于表单字段,Part和PartMap与@multipart注解结合使用,适合文件上传的情况 |
@PartMap | 用于表单字段,默认接受类型是Map<String,RequestBody>,可用于实现多文件上传 |
@Path | 用于Url中的占位符 |
@Query | 用于Get请求中的参数 |
@QueryMap | 与Query类似,用于不确定表单参数 |
@Url | 指定请求路径 |
项目代码实践
Get请求
获取人员信息service:
@Autowired
private OpenApi openApi;
public UserMeta getUserFromOpenApi(String name) throws BondingException {
OpenApiResult<UserMeta> result;
try {
result = openApi.getUserFromOpenApi(name).execute().body();
log.info("getUserFromOpenApi, name: {}, result: {}", name, result);
} catch (Exception e) {
log.error("getUserMetaFromOpenApi error", e);
throw new Exception(RELY_SERVICE_BUSY);
}
if (result == null || result.getStatus() != SUCCESS_STATUS) {
throw new Exception(RELY_SERVICE_BUSY);
}
HTTP接口,通过@Path注解可以给URL动态赋值:
public interface OpenApi {
/**
* 获取人员信息
*/
@GET("/v2/user/user/{username}")
Call<OpenApiResult<UserMeta>> getUserFromOpenApi(@Path("username") String username);
}
根据用户名模糊查询公司员工信息,返回前N条数据service:
public class IHRService {
@Autowired
private OpenApi openApi;
public JSONArray getUserInfoList(String username, int limit) throws BondingException {
try {
JsonArray result =
openApi.getUserMetaFromOpenApi(username, limit).execute().body();
JSONArray responseJson = new Gson().fromJson(result, JSONArray.class);
return responseJson;
} catch (Exception e) {
log.error("getUserInfoList error", e);
throw new Exception(RELY_SERVICE_BUSY);
}
}
}
HTTP接口,通过@Query接口传递GET中参数:
public interface OpenApi {
@GET("/v1/person")
Call<JsonArray> getUserMetaFromOpenApi(
@Query("name") String name,
@Query("limit") int limit);
}
批量查询Team任务信息service:
/**
* 批量查询任务信息(包含状态)
*/
public Map<String, String> batchGetTask(List<String> taskIdList, String operator)
throws BondingException {
Map<String, String> resultMap = new HashMap<>();
try {
String url = "external/task/taskInfos?operator="
+ operator + "&taskIds=" + String.join("&taskIds=", taskIdList);
OpenApiResult<List<TaskInfoResult>> result = openApi.batchGetTaskInfo(url).execute().body();
if (result == null || result.getStatus() != SUCCESS_STATUS) {
throw new Exception(RELY_SERVICE_BUSY);
}
result.getResult().forEach(taskBaseModel -> {
TaskBaseModel baseModel = taskBaseModel.getTaskBaseModel();
resultMap.put(baseModel.getTaskId(), baseModel.getStatusPhase());
});
return resultMap;
} catch (Exception e) {
log.error("batchGetTask error", e);
throw new Exception(RELY_SERVICE_BUSY);
}
}
HTTP接口,通过@Url注解可以直接指定接口路径:
public interface OpenApi {
/**
* 批量查询任务信息(包含状态)
*/
@GET
Call<OpenApiResult<List<TaskInfoResult>>> batchGetTaskInfo(@Url String url);
}
POST请求
HTTP接口,@Header注解用户增加Header,@Body注解用于POST请中的请求体
public interface OpenApi {
/**
* 新增某个角色对用户的授权
*/
@POST("/api/roles/{roleCode}/users")
Call<OpenApiResult<String>> addUsersForRole(
@Header("AMC-User-Type") String type,
@Path("roleCode") String roleCode,
@Body List<String> authUserList);
}
HTTP接口,@Headers注解用于添加固定请求头,可以同时添加多个:
public interface OpenApi {
/**
* 批量获取人员信息
*/
@POST("/openapi/v2/usernames")
@Headers({"Content-Type: application/json;charset=utf-8"})
Call<OpenApiResult<List<UserMeta>>> batchGetUserMetaFromOpenApi(@Body List<String> userNameList);
}
DELETE请求
HTTP接口,@DELETE注解默认情况下是不能够有Body的,如果想要有Body的DELETE请求,需要通过@HTTP注解的方式:
public interface OpenApi {
/**
* 取消某个角色对某用户的授权
*/
@HTTP(method = "DELETE", path = "/api/roles/{roleCode}/users", hasBody = true)
Call<OpenApiResult<String>> revokeUsersForRole(
@Header("type") String type,
@Path("roleCode") String roleCode,
@Body List<String> authUserList);
}