Android Retrofit2框架的使用,以及解析複雜Json(其實也不算太複雜)
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進行封裝做篇幅,謝謝~
附上:原始碼!