Retrofit是Square公司的开源项目,是基于OKHttp进行的应用层封装
Retrofit官方文档:http://square.github.io/retrofit/
Retrofit GitHub地址:https://github.com/square/retrofit
如何使用Retrofit?
1、引入Retrofit2依赖库
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
// OkHttp
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
2、当然不能忘记 AndroidMainfest.xml 中的网络权限
// 网络权限
<uses-permission android:name="android.permission.INTERNET" />
3、请求接口,SoJson的免费农历查询API接口:https://www.sojson.com/api/lunar.html,该接口只支持GET请求
如:https://www.sojson.com/open/api/lunar/json.shtml?date=2018-10-1
返回json:
{
"status": 200,
"message": "success",
"data": {
"year": 2018,
"month": 10,
"day": 1,
"lunarYear": 2018,
"lunarMonth": 8,
"lunarDay": 22,
"cnyear": "贰零壹捌 ",
"cnmonth": "八",
"cnday": "廿二",
"hyear": "戊戌",
"cyclicalYear": "戊戌",
"cyclicalMonth": "辛酉",
"cyclicalDay": "丙寅",
"suit": "祭祀,冠笄,会亲友,拆卸,起基,除服,成服,移柩,启钻,安葬,沐浴,捕捉,开光,塑绘",
"taboo": "作灶,祭祀,入宅,嫁娶",
"animal": "狗",
"week": "Monday",
"festivalList": ["国庆节"],
"jieqi": {
"9": "寒露",
"24": "霜降"
},
"maxDayInMonth": 29,
"leap": false,
"lunarYearString": "戊戌",
"bigMonth": false
}
}
Retrofit第一步:创建网络请求接口 HttpService.java
public interface HttpService {
/**
* GET请求,合并后URL为:https://www.sojson.com/open/api/lunar/json.shtml?date=xxx
* @param date 日期参数
* @return 返回值Call,泛型ResponseBody
*/
@GET("/open/api/lunar/json.shtml")
Call<ResponseBody> setGETParameter(@Query("date") String date);
}
Retrofit第二步:在调用网络请求的代码中(如 MainActivity.java),创建Retrofit对象,创建接口对象,设置网络请求参数放回Call对象
// 创建Retrofit
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl("https://www.sojson.com/")
.build();
// 创建服务接口
HttpService service = retrofit.create(HttpService.class);
// 设置参数,返回Call对象
final Call<ResponseBody> call = service.setGETParameter("2018-10-1");
Retrofit第三步:执行网络请求
同步请求方式:不能再主线程中执行,否则将抛出:android.os.NetworkOnMainThreadException
// 同步方式
new Thread(new Runnable() {
@Override
public void run() {
try {
Response<ResponseBody> responseBody = call.execute();
Log.e("my_info", responseBody.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
异步请求方式:通过回调的方式处理请求结果
// 异步方式
call.enqueue(new Callback<ResponseBody>() {
// 成功回调
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e("my_info", response.toString());
}
// 失败回调
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e("my_info", t.toString());
}
});
经过这几步之后,就完成Retrofit的网络请求了,请求结果Log如下:
E/my_info: Response{protocol=h2, code=403, message=, url=https://www.sojson.com/open/api/lunar/json.shtml?date=2018-10-1}
问题来了,响应码为啥不是200,而是403?
百度:403错误是网站访问过程中,常见的错误提示。资源不可用。服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致,比如IIS或者apache设置了访问权限不当。一般会出现以下提示:403错误。关闭了IE的"显示友好的HTTP错误",显示没有权限访问。
我:服务器有保护机制,拒绝了你的请求。
此时,请求结果的response对象的body是空的,怎样才能好好get数据呢?正确姿势如下:
加入请求头,伪装成Chrome浏览器,通过OKHttp的拦截器的方式加入请求头,OKHttp的拦截器又分为应用拦截器和网络拦截器(后面有时间再开篇幅),这里使用的是应用拦截器
在创建Retrofit对象前创建OkHttpClient对象,并通过addInterceptor方法设置应用拦截器
// 创建OkHttpClient.Builder对象
OkHttpClient.Builder builder = new OkHttpClient().newBuilder() ;
// 设置拦截器
builder.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
// 设置Header
Request newRequest = chain.request().newBuilder()
.removeHeader("User-Agent")
.addHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36")
.build() ;
return chain.proceed(newRequest);
}
}) ;
// 获取OkHttpClient对象
OkHttpClient client = builder.build();
修改创建Retrofit对象的代码,设置Client为上面创建的OkHttpClient对象:
// 创建Retrofit
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl("https://www.sojson.com/")
.client(client)
.build();
重新运行,结果Log如下:
E/my_info: Response{protocol=h2, code=200, message=, url=https://www.sojson.com/open/api/lunar/json.shtml?date=2018-10-1}
这时候我们可以输出一下请求回来的数据,在请求成功的回调中加入Log:
// 成功回调
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.e("my_info", response.toString());
try {
Log.e("my_info", response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
E/my_info: Response{protocol=h2, code=200, message=, url=https://www.sojson.com/open/api/lunar/json.shtml?date=2018-10-1}
{"status":200,"message":"success","data":{"year":2018,"month":10,"day":1,"lunarYear":2018,"lunarMonth":8,"lunarDay":22,"cnyear":"贰零壹捌 ","cnmonth":"八","cnday":"廿二","hyear":"戊戌","cyclicalYear":"戊戌","cyclicalMonth":"辛酉","cyclicalDay":"丙寅","suit":"祭祀,冠笄,会亲友,拆卸,起基,除服,成服,移柩,启钻,安葬,沐浴,捕捉,开光,塑绘","taboo":"作灶,祭祀,入宅,嫁娶","animal":"狗","week":"Monday","festivalList":["国庆节"],"jieqi":{"9":"寒露","24":"霜降"},"maxDayInMonth":29,"leap":false,"lunarYearString":"戊戌","bigMonth":false}}
上面的是一个简单的GET请求,下面在看看POST如何进行:
在请求接口 HttpService.java 中增加POST接口配置,声明注解为@POST 和 @FormUrlEncoded,参数注解为@Field,代码:
public interface HttpService {
/**
* GET请求,合并后URL为:https://www.sojson.com/open/api/lunar/json.shtml?date=xxx
* @param date 日期参数
* @return 返回值Call,泛型ResponseBody
*/
@GET("/open/api/lunar/json.shtml")
Call<ResponseBody> setGETParameter(@Query("date") String date);
/**
* POST请求
*/
@POST("/open/api/lunar/json.shtml")
@FormUrlEncoded
Call<ResponseBody> setPOSTParameter(@Field("date") String date);
}
如果缺省 @FormUrlEncoded将抛出以下异常:
java.lang.IllegalArgumentException: @Field parameters can only be used with form encoding. (parameter #1)
设置网络请求参数放回Call对象的时候,调用setPOSTParameter设置参数即可
// 设置参数,返回Call对象
// final Call<ResponseBody> call = service.setGETParameter("2018-10-1");
final Call<ResponseBody> call = service.setPOSTParameter("2018-10-1");
其他和GET方式一致,因为sojson的农历接口不支持POST请求,这里就点到为止了
E/my_info: Response{protocol=h2, code=405, message=, url=https://www.sojson.com/open/api/lunar/json.shtml}
------------------------------------------------------------------华丽的分割线,不要问我华丽是谁------------------------------------------------------------------
上面仅仅是简单的请求,又怎能满足各种刁钻的开发需求呢,Retrofit提供了json的解析器,对请求返回的json字符串进行自动解析封装。
首先引入依赖:
// Retrofit的GSON解析
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
JSON转实体类,将返回的JSON数据格式,封装成Bean实体类,可以看到上面的JSON比较长,数据也比一般格式复杂,作为优秀的码农是不会一个个字符手敲的!!使用Android Studio的GsonFormat插件可以快速的帮生成代码,至于用法,这里就不介绍了。
JSON分析:因为上面返回的
"jieqi": {
"9": "寒露",
"24": "霜降"
},
JSON内容的键与值是不确定的,对于少量的可能,我们可以在实体对象当中穷举,但是对于上面的穷举似乎没有人会去做吧.....
解决方法:将这个带不确定性的字段,封装成JsonObject类型,后续拿到Bean实体对象的时候再进行Json遍历处理。(如有更好的方案欢迎指教,感激不尽)
最终调整的Bean实体代码如下 Lunar.java:
public class Lunar{
private int status;
private String message;
private DataBean data;
// 此处省略set、get和toString代码
public static class DataBean {
private int year;
private int month;
private int day;
private int lunarYear;
private int lunarMonth;
private int lunarDay;
private String cnyear;
private String cnmonth;
private String cnday;
private String hyear;
private String cyclicalYear;
private String cyclicalMonth;
private String cyclicalDay;
private String suit;
private String taboo;
private String animal;
private String week;
private JsonObject jieqi;
private int maxDayInMonth;
private boolean leap;
private String lunarYearString;
private boolean bigMonth;
private List<String> festivalList;
// 此处省略set、get和toString代码
}
将请求接口 HttpService.java 和 请求调用Call的泛型 ResponseBody 改成 Lunar
// /**
// * GET请求,合并后URL为:https://www.sojson.com/open/api/lunar/json.shtml?date=xxx
// * @param date 日期参数
// * @return 返回值Call,泛型ResponseBody
// */
// @GET("/open/api/lunar/json.shtml")
// Call<ResponseBody> setGETParameter(@Query("date") String date);
@GET("/open/api/lunar/json.shtml")
Call<Lunar> setGETParameter(@Query("date") String date);
// 设置参数,返回Call对象
// final Call<ResponseBody> call = service.setGETParameter("2018-10-1");
// final Call<ResponseBody> call = service.setPOSTParameter("2018-10-1");
final Call<Lunar> call = service.setGETParameter("2018-10-1");
执行请求代码也一样需要修改:
//异步方式
call.enqueue(new Callback<Lunar>() {
// 成功回调
@Override
public void onResponse(Call<Lunar> call, Response<Lunar> response) {
Log.e("my_info", response.toString());
Lunar lunar = response.body();
Log.e("my_info", lunar.toString());
}
//失败回调
@Override
public void onFailure(Call<Lunar> call, Throwable t) {
Log.e("my_info", t.toString());
}
});
不要忘记在创建Retrofit对象的时候加入解析工厂:
// 创建Retrofit
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl("https://www.sojson.com/")
.client(client)
// Gson解析工厂
.addConverterFactory(GsonConverterFactory.create())
.build();
再次运行,结果Log如下:
E/my_info: Response{protocol=h2, code=200, message=, url=https://www.sojson.com/open/api/lunar/json.shtml?date=2018-10-1}
Lunar{status=200, message='success', data=DataBean{year=2018, month=10, day=1, lunarYear=2018, lunarMonth=8, lunarDay=22, cnyear='贰零壹捌 ', cnmonth='八', cnday='廿二', hyear='戊戌', cyclicalYear='戊戌', cyclicalMonth='辛酉', cyclicalDay='丙寅', suit='祭祀,冠笄,会亲友,拆卸,起基,除服,成服,移柩,启钻,安葬,沐浴,捕捉,开光,塑绘', taboo='作灶,祭祀,入宅,嫁娶', animal='狗', week='Monday', jieqi={"9":"寒露","24":"霜降"}, maxDayInMonth=29, leap=false, lunarYearString='戊戌', bigMonth=false, festivalList=[国庆节]}}
Retrofit2的使用就到这里了,后面有时间将会对Retrofit + OKHttp + RxJava进行封装做篇幅,谢谢~
附上:源代码!